Home > programming > lthread代码分析(二)

lthread代码分析(二)

上回介绍了一些lthread的重要的数据结构,还有lthread是如何切换cpu执行指令的位置来达到任务切换的,现在来看看lthread的一些基本操作。不过说了这么多lthread到底是一个什么东西呢? 简单的说,lthread是一个用户态的轻量级线程,并且是不可比强制切换的哪一种。只有在lthread的执行过程中主动调用了一些函数,例如lthread_sleeping之类的函数,才会由sched来进行任务切换。也就是说lthread是一个non-preempty thread。

上回已经说过了一个lthread的数据结构是_lthread,当我们需要使用lthread的时候,首先需要创建一个lthread的实例 _lthread_create就是创建这个lthread实例的,类似于pthread_create这种函数.

int
lthread_create(lthread_t **new_lt, void *fun, void *arg)
{
    lthread_t *lt = NULL;
    pthread_once(&key_once, _lthread_key_create);
    sched_t *sched = lthread_get_sched();
    /*
    new_lt 这个是被创建的lthread的指针,成功以后就可以使用*new_lt进行lthread操作
    fun    和pthread的意思一样,就是lthread的执行函数
    arg    和pthread的意思一样,这个值将会传入你的提供的lthread执行函数
    */
    if (sched == NULL) {
        sched_create(0);/*创建一个调度器,这个就会在以后有所说明*/
        sched = lthread_get_sched();
        if (sched == NULL) {
            perror("Failed to create scheduler");
            return -1;
        }
    }

    if ((lt = calloc(1, sizeof(lthread_t))) == NULL) {
        perror("Failed to allocate memory for new lthread");
        return errno;
    }
    /*
    如果没有调度器话那么就创建一个,一个系统线程只会有一个调度器,所有在这个系统线程上运行的lthread都由这个调度器进行调度操作
    */
    lt->sched = sched;
    lt->stack_size = 0;
    lt->state = bit(LT_NEW);
    lt->id = sched->total_lthreads++;
    lt->fun = fun;
    lt->fd_wait = -1;
    lt->arg = arg;
    lt->birth = rdtsc();
    lt->timeout = -1;
    *new_lt = lt;
    LIST_INSERT_HEAD(&lt->sched->new, lt, new_next);
     /*
     为lthread分配内存,并且初始化一些参数。
     将当前新建的lthread的调度器指针指向当前系统线程拥有的调度器上, 初始化stack_size为0,这个时候lthread并未执行,并且将状态置为new
     记录下线程创建时间。然后把新建的线程记录到调度器的new lthread list中去,以便调度器操作
    */
    return 0;
}

上面的lthreadcreate中调用了sched_create用于创建lthread的调度器,现在我们来看看调度器是如何创建的

int
sched_create(size_t stack_size)
{
    sched_t *new_sched;
    size_t sched_stack_size = 0;
    /*选择一个栈空间大小,不设置的话就用8M这个缺省值*/
    sched_stack_size = stack_size ? stack_size : MAX_STACK_SIZE;

    if ((new_sched = calloc(1, sizeof(sched_t))) == NULL) {
        perror("Failed to initialize schedulern");
        return errno;
    }
    /*将调度器设置到系统线程的TLS中,以便在当前的系统线程的任何一个地方都可以使用*/
    pthread_setspecific(lthread_sched_key, new_sched);
    /*为调度器分配栈空间大小,其实所有这个调度器运行的的lthread都会使用这个栈*/
    if ((new_sched->stack = calloc(1, sched_stack_size)) == NULL) {
        free(new_sched);
        perror("Failed to initialize schedulern");
        return errno;
    }

    bzero(new_sched->stack, sched_stack_size);/*不知道为何要执行这个操作,感觉没有什么必要*/
    /*
     创建一个poller用于获取网络IO的事件,以后估计会有File IO的事件。
    */
    if ((new_sched->poller = create_poller()) == -1) {
        perror("Failed to initialize pollern");
        _sched_free(new_sched);
        return errno;
    }

    if (pthread_mutex_init(&new_sched->compute_mutex, NULL) != 0) {
        perror("Failed to initialize compute_mutexn");
        _sched_free(new_sched);
        return errno;
    } 
    /*创建pipe,这样的好处是即便当前没有任何网络事件,也可以用这个pipe来唤醒调度器*/
    if (pipe(new_sched->compute_pipes) == -1) {
        perror("Failed to initialize pipen");
        _sched_free(new_sched);
        return errno;
    }

    new_sched->stack_size = sched_stack_size;

    new_sched->total_lthreads = 0;
    new_sched->default_timeout = 3000000u;
    new_sched->waiting_state = 0;
    new_sched->sleeping_state = 0;
    new_sched->sleeping = RB_ROOT;
    new_sched->birth = rdtsc();
    LIST_INIT(&new_sched->new);
    *将调度器的cpu state清空,调用lthread_run并且有lthread需要运行的话,这个st将会被设置*/
    bzero(&new_sched->st, sizeof(struct _cpu_state));

    return 0;
}

当lthread以及相关的sched的实例都创建好了,我们就可以使用lthread_run来执行了

void
lthread_run(void)
{
    sched_t *sched;
    lthread_t *lt = NULL, *lttmp = NULL;
    int p = 0;
    int fd = 0;
    int ret = 0;
    (void)ret; /* silence compiler */

    sched = lthread_get_sched();/*从系统线程中取得设置的调度器,每一个系统线程至多有一个调度器*/

    while (sched->sleeping_state ||
        !LIST_EMPTY(&sched->new) ||
        sched->waiting_state) {/*如果有lthread处于sleep状态,等待网络事件状态,或者有新建的lthread就一直执行,否则退出*/

        /*
         运行那些过期的lthread,例如某一个lthread设置10s以后再次被执行,那么如果现在已经过了10s,那么这个
         lthread就需要被执行。这个是一种设置timer的好方法
        */
        _resume_expired_lthreads(sched);

        /*运行那些被lthread_create创建的lthread实例*/
        while (!LIST_EMPTY(&sched->new)) {
            LIST_FOREACH_SAFE(lt, &sched->new, new_next, lttmp) {
                LIST_REMOVE(lt, new_next);
                _lthread_resume(lt);
            }
        }

        /* 3. resume lthreads we received from lthread_compute, if any */
        while (1) {
            pthread_mutex_lock(&sched->compute_mutex);
            lt = LIST_FIRST(&sched->compute);
            if (lt == NULL) {
                pthread_mutex_unlock(&sched->compute_mutex);
                break;
            }
            LIST_REMOVE(lt, compute_sched_next);
            pthread_mutex_unlock(&sched->compute_mutex);
            sched->sleeping_state--;
            _lthread_resume(lt);
        }
        /*向poller注册感兴趣的事件,这样当有事件发生的时候,就会得到通知*/
        register_rd_interest(sched->compute_pipes[0]);
        _lthread_poll();/*等待事件发生或者timeout*/

        /* 5. fire up lthreads that are ready to run */
        while (sched->total_new_events) {/*如果有网络事件发生就一一找到对应的lthread执行*/
            p = --sched->total_new_events;

            /* We got signaled via pipe to wakeup from polling & rusume compute.
             * Those lthreads will get handled in step 3.
             */
            fd = get_fd(&sched->eventlist[p]);
            if (fd == sched->compute_pipes[0]) {
                ret = read(fd, &tmp, sizeof(tmp));
                continue;
            }

            lt = (lthread_t *)get_data(&sched->eventlist[p]);
            if (lt == NULL)
                assert(0);

            if (is_eof(&sched->eventlist[p]))
                lt->state |= bit(LT_FDEOF);

            _desched_lthread(lt);
            _lthread_resume(lt);
        }
    }

    _sched_free(sched);

    return;
}

以上部分中,cpustate在执行过程中起到了关键的作用,当任务调度器发现有lthread需要执行的时候将会将CPU切换到需要执行Lthread的cpustate上,这个lthread执行完毕以后又会将CPU切换到调度器的cpustate上。于是调度器就继续执行剩下的事情,找寻下一个需要执行的lthread或者等待事件发生。lthread 运行示意图当没有任何需要执行的lthread存在以后调度器的运行代码就会退出(实际上是lthreadrun这个函数退出)。

Categories: programming Tags: ,
  1. No comments yet.
  1. No trackbacks yet.