ST源码分析-协程通信
在讲解之前,推荐阅读 State-thread 的官方文章,每个函数的使用在文档都有讲解。《State-thread函数使用文档》
在 Linux 系统使用 多线程的时候,线程间通信,可以使用 条件变量 以及 互斥锁。例如 线程 A 是生产者,不断写入任务到队列,线程 B 是消费者,不断从队列读取任务,没有任务的时候,线程B会阻塞,等待 线程A通知。这个通知就需要用到 条件变量 以及 互斥锁。
pthread_mutex_lock()
以及 pthread_cond_wait()
ST 的协程 跟线程类似,也需要处理这种 生产者 消费者问题。因此 ST 提供了跟 线程类似的函数。如下:
下面就用一小段代码,演示这些函数的使用。
#include #include #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()
函数。
相关阅读:
- 《State-thread函数使用文档》
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。QQ:2338195090。
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习]