ST源码分析-协程通信 - 弦外之音

/ 0评 / 0

在讲解之前,推荐阅读 State-thread 的官方文章,每个函数的使用在文档都有讲解。《State-thread函数使用文档》

在 Linux 系统使用 多线程的时候,线程间通信,可以使用 条件变量 以及 互斥锁。例如 线程 A 是生产者,不断写入任务到队列,线程 B 是消费者,不断从队列读取任务,没有任务的时候,线程B会阻塞,等待 线程A通知。这个通知就需要用到 条件变量 以及 互斥锁

pthread_mutex_lock() 以及 pthread_cond_wait()



ST 的协程 跟线程类似,也需要处理这种 生产者 消费者问题。因此 ST 提供了跟 线程类似的函数。如下:

下面就用一小段代码,演示这些函数的使用。

#include <stdio.h>
#include <memory.h>
#include "st.h"
#include "../common.h"
_st_cond_t *cond_name;
_st_mutex_t* mutex_name;
char name[100] = {};
​
void *publish(void *arg) {
    st_usleep(2 * 1000000LL);
    st_cond_signal(cond_name);
​
    strcpy(name,"Loken");
    st_utime_t time_now = st_utime();
    printf("Pulish name %lld\r\n", time_now);
​
    return NULL;
}
​
void *consume(void *arg) {
    st_mutex_lock(mutex_name);
    st_cond_wait(cond_name);
    st_mutex_unlock(mutex_name);
​
    st_utime_t time_now = st_utime();
    printf("Consume name %s , %lld\r\n",name, time_now);
    return NULL;
}
​
​
int main(int argc, char *argv[]) {
    cond_name = st_cond_new();
    mutex_name = st_mutex_new();
    st_init();
​
    st_utime_t time_now = st_utime();
    printf("start %lld\r\n", time_now);
​
    st_thread_create(publish, NULL, 0, 0);
    st_thread_create(consume, NULL, 0, 0);
    st_thread_create(consume, NULL, 0, 0);
    st_thread_create(consume, NULL, 0, 0);
​
    st_thread_exit(NULL);
​
    /* NOTREACHED */
    return 1;
}

运行结果如下图:

我上面代码,创建了 3 个消费这协程,但是只有一个协程被唤醒了,所以测试成功。


下面就来分析一下 ST 的条件变量以及互斥锁的实现原理。

先讲解一些 互斥锁的实现,请看下图:

从上图可以看到,互斥锁,实际上就是 _st_clist_t_st_thread_t 。再来看一下 st_mutex_lock() 函数的实现。请看下图:

从上图可以看出,只有一个协程能从 st_mutex_lock() 返回,其他的协程都会阻塞在 st_mutex_lock()。这里普及一下,因为 ST 是单线程多协程,所以从系统角度来看,无论什么时候,都是只有一个协程在运行。 st_mutex_lock() 内部在 _ST_SWITCH_CONTEXT(me) 的时候就切换到其他协程去运行,那什么时候切换回来呢?请继续看下图。

没错,上图是 st_mutex_unlock() 的代码,解锁内部逻辑 会 从 lock->wait_q 协程队列里面取一个出来,把状态从 _ST_ST_LOCK_WAIT 改成 _ST_ST_RUNNABLE,这样被 st_mutex_lock() 阻塞的协程下次调度就能重新跑起来。


下面来讲解 条件变量 的实现原理 ,请看下图:

从上图可以看到,条件变量,里面实际上是一个 _st_clist_t

st_cond_wait() 的实现也很简单,如下:

int st_cond_wait(_st_cond_t *cvar)
{
  return st_cond_timedwait(cvar, ST_UTIME_NO_TIMEOUT);
}

所以,重点只需要分析 st_cond_timedwait() 函数就行了,请看下图:

从上图可以看出, st_cond_timedwait() 函数 只有一个重点,就是把协程状态修改为 _ST_ST_COND_WAIT,再把当前协程加进去 cvar->wait_q 队列里面,然后切换上下文,开始调度,调度只会取 _ST_ST_RUNNABLE 的协程进来运行。哪个协程 是 _ST_ST_RUNNABLE 的?就是 publish() 函数。此时,上下文切换就会跳到 publish() 函数运行。publish() 函数会执行 st_cond_signal(),下面分析 st_cond_signal() 函数的实现。

上图代码的重点,就是 从 cvar->wait_q 队列拿一个协程出来,改成 _ST_ST_RUNNABLE,然后加进去 RUNQ 队列,非广播只会取一个协程。


ST 的互斥锁与条件变量分析完毕,由于 ST 是单线程的,所以多协程操作全局变量,实际上不需要加锁,因为无论何时何地实际上只有一个协程在运行。所以我上面的代码实际上是不需要使用 st_mutex_lock() 函数。


相关阅读:

  1. 《State-thread函数使用文档》

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。QQ:2338195090。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注