Subscribed unsubscribe Subscribe Subscribe

軽量ユーザスレッドを実現するlibtaskを試してみた。

libtask coroutine fiber

libtask。コルーチンとスケジューラがセットになっているのか。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "task.h"

typedef int err_t;

typedef struct {
    char *name;
} my_task_t;

void my_task_run(my_task_t *ctx)
{
    printf("Hi, I am a lightweight task %s (id=%d)\n", ctx->name,
            taskid());
    for (int i = 0; i < 3; ++i) {
        printf("You see %s is just doing well man!\n", ctx->name);
        taskyield();
    }
}

err_t my_task_init(my_task_t *ctx, const char *name)
{
    ctx->name = strdup(name);
    if (!ctx->name)
        return -1;
    return 0;
}

void my_task_destroy(my_task_t *ctx)
{
    free(ctx->name);
}

void my_task_release(my_task_t *ctx)
{
    my_task_destroy(ctx);
    free(ctx);
}

err_t my_task_new(my_task_t **retval, const char *name)
{
    my_task_t *ctx;
    err_t err;
    ctx = malloc(sizeof(*ctx));
    if (!ctx)
        return -1;
    err = my_task_init(ctx, name);
    if (err) {
        free(ctx);
        return err;
    }
    *retval = ctx;
    return 0;
}

static const char* names[10] = {
    "Connor",
    "Conrad",
    "Conroy",
    "Conway",
    "Corwin",
    "Crosby",
    "Culbert",
    "Culver",
    "Curt",
    "Curtis"
};

void taskmain(int argc, char **argv)
{
    const unsigned int some_appropriate_stack_size = getpagesize(); // WTF?
    my_task_t *tasks[10];
    struct {
        unsigned int id;
    } scoreboard[sizeof(tasks) / sizeof(*tasks)];

    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i)
        tasks[i] = 0;


    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        if (my_task_new(&tasks[i], names[i]))
            goto out;
    }
   
    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        scoreboard[i].id = taskcreate((void(*)(void*))my_task_run, tasks[i],
                some_appropriate_stack_size);
    }

    taskexit(0);

out:
    for(int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        if (tasks[i])
            my_task_release(tasks[i]);
    }
    taskexitall(0);
}

大体同じ事を Windows でやると次のようになった。
runinfo_t というのが連結リストになってるのがミソ。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

LPVOID systemFiber;
int num_tasks = 0;

typedef struct _runinfo_t {
    LPFIBER_START_ROUTINE entry;
    void *ctx;
    int finished;
    LPVOID id;
    struct _runinfo_t *prev;
    struct _runinfo_t *next;
} runinfo_t;

runinfo_t *first_runinfo = 0, *last_runinfo = 0;

void loop()
{    
    while (num_tasks > 0) {
        for (runinfo_t *ri = first_runinfo, *next; ri; ri = next) {
            next = ri->next;

            if (ri->finished) {
                if (ri->prev)
                    ri->prev->next = ri->next;
                else
                    first_runinfo = ri->next;
                if (ri->next)
                    ri->next->prev = ri->prev;
                else
                    last_runinfo = ri->prev;
                DeleteFiber(ri->id);
                free(ri);
                --num_tasks;
                continue;
            }

            SwitchToFiber(ri->id);
        }
    }
}

typedef struct {
    int argc;
    char **argv;
} main_arg_t;

void taskyield()
{
    SwitchToFiber(systemFiber);
}

void starter(runinfo_t *ri)
{
    ri->entry(ri->ctx);
    ri->finished = 1;
    taskyield();
}

LPVOID taskcreate(LPFIBER_START_ROUTINE entry, void *ctx, DWORD stacksize)
{
    runinfo_t *ri = (runinfo_t*)malloc(sizeof(runinfo_t));
    if (!ri)
        abort();
    ri->entry = (LPFIBER_START_ROUTINE)entry;
    ri->ctx =  ctx;
    ri->finished = 0;
    ri->next = 0;
    ri->id = CreateFiber(
            stacksize,
            (LPFIBER_START_ROUTINE)starter, ri);
    if (!ri->id)
        abort();
    
    ri->prev = last_runinfo;
    if (last_runinfo)
        last_runinfo->next = ri;
    else
        first_runinfo = ri;
    last_runinfo = ri;
    num_tasks++;

    return ri;
}

void taskexit()
{
    runinfo_t *ri = (runinfo_t*)GetFiberData();
    ri->finished = 1;
    taskyield();
}

void taskmain(int argc, char **argv);

void taskmainrunner(main_arg_t *arg)
{
    taskmain(arg->argc, arg->argv);
}

int main(int argc, char **argv)
{
    systemFiber = ConvertThreadToFiber(NULL);
    if (!systemFiber) {
        printf("doh! something went wrong.\n");
        return -1;
    }

    main_arg_t arg = { argc, argv }; 
    taskcreate((LPFIBER_START_ROUTINE)taskmainrunner, &arg, 0);
    loop();

    DeleteFiber(systemFiber);
}

typedef int err_t;

typedef struct {
    char *name;
} my_task_t;

void my_task_run(my_task_t *ctx)
{
    printf("Hi, I am a lightweight task %s (id=%p)\n", ctx->name,
            GetCurrentFiber());
    for (int i = 0; i < 3; ++i) {
        printf("You see %s is just doing well man!\n", ctx->name);
        taskyield();
    }
}

err_t my_task_init(my_task_t *ctx, const char *name)
{
    ctx->name = strdup(name);
    if (!ctx->name)
        return -1;
    return 0;
}

void my_task_destroy(my_task_t *ctx)
{
    free(ctx->name);
}

void my_task_release(my_task_t *ctx)
{
    my_task_destroy(ctx);
    free(ctx);
}

err_t my_task_new(my_task_t **retval, const char *name)
{
    my_task_t *ctx;
    err_t err;
    ctx = (my_task_t*)malloc(sizeof(*ctx));
    if (!ctx)
        return -1;
    err = my_task_init(ctx, name);
    if (err) {
        free(ctx);
        return err;
    }
    *retval = ctx;
    return 0;
}

static const char* names[10] = {
    "Connor",
    "Conrad",
    "Conroy",
    "Conway",
    "Corwin",
    "Crosby",
    "Culbert",
    "Culver",
    "Curt",
    "Curtis"
};

void taskmain(int argc, char **argv)
{
    my_task_t *tasks[10];
    struct {
        LPVOID id;
    } scoreboard[sizeof(tasks) / sizeof(*tasks)];

    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        tasks[i] = 0;
        scoreboard[i].id = 0;
    }


    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        if (my_task_new(&tasks[i], names[i]))
            goto out;
    }
   
    for (int i = 0; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        scoreboard[i].id = taskcreate(
                (LPFIBER_START_ROUTINE)my_task_run, tasks[i],
                0);
    }

    taskexit();

out:
    for(int i = 0 ; i < sizeof(tasks) / sizeof(*tasks); ++i) {
        if (tasks[i]) {
            my_task_release(tasks[i]);
        }
    }
}

いずれの例も次のようなテキストを出力する。

Hi, I am a lightweight task Connor (id=001436F8)
You see Connor is just doing well man!
Hi, I am a lightweight task Conrad (id=001439E8)
You see Conrad is just doing well man!
Hi, I am a lightweight task Conroy (id=00143CD8)
You see Conroy is just doing well man!
Hi, I am a lightweight task Conway (id=00143FC8)
You see Conway is just doing well man!
Hi, I am a lightweight task Corwin (id=001442B8)
You see Corwin is just doing well man!
Hi, I am a lightweight task Crosby (id=001445A8)
You see Crosby is just doing well man!
Hi, I am a lightweight task Culbert (id=00144898)
You see Culbert is just doing well man!
Hi, I am a lightweight task Culver (id=00144B88)
You see Culver is just doing well man!
Hi, I am a lightweight task Curt (id=00144E78)
You see Curt is just doing well man!
Hi, I am a lightweight task Curtis (id=00145168)
You see Curtis is just doing well man!
You see Connor is just doing well man!
You see Conrad is just doing well man!
You see Conroy is just doing well man!
You see Conway is just doing well man!
You see Corwin is just doing well man!
You see Crosby is just doing well man!
You see Culbert is just doing well man!
You see Culver is just doing well man!
You see Curt is just doing well man!
You see Curtis is just doing well man!
You see Connor is just doing well man!
You see Conrad is just doing well man!
You see Conroy is just doing well man!
You see Conway is just doing well man!
You see Corwin is just doing well man!
You see Crosby is just doing well man!
You see Culbert is just doing well man!
You see Culver is just doing well man!
You see Curt is just doing well man!
You see Curtis is just doing well man!