在讲解之前,推荐阅读 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()
函数。
相关阅读:
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。QQ:2338195090。