最近尝试将围棋AI程序中蒙托卡罗搜索与GPU使用分开,使用多线程尽量压榨机器性能,那么必然涉及到生产-消费模型中通信的队列。
网上查阅到的两种方式,一种是线程条件变量,一种是利用eventfd实现事件通知 (源自推酷)。
简单的环形队列实现见附录1。
线程条件变量
相关函数介绍
pthread_cond_init:初始化一个线程条件变量。
pthread_cond_wait:等待条件触发。
pthread_cond_signal:通知一个线程,线程条件发生。
pthread_cond_timedwait:等待条件触发,可以设置超时时间。
pthread_cond_reltimedwait_np:和pthread_cond_timedwait使用基本相同,区别是使用的是相对时间间隔而不是绝对时间间隔。
pthread_cond_broadcast:通知所有等待线程,线程条件发生。
pthread_cond_destroy:销毁条件变量。
唤醒丢失问题
如果线程未持有与条件相关联的互斥锁,则调用 pthread_cond_signal() 或 pthread_cond_broadcast() 会产生唤醒丢失错误。满足以下所有条件时,即会出现唤醒丢失问题:
一个线程调用 pthread_cond_signal() 或 pthread_cond_broadcast()
另一个线程已经测试了该条件,但是尚未调用 pthread_cond_wait()
没有正在等待的线程
信号不起作用,因此将会丢失,仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。只要仅在持有关联的互斥锁同时修改所测试的条件,即可调用 pthread_cond_signal() 和 pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。
线程条件变量使用方法
|
|
线程变量实现的异步队列
|
|
Event事件
eventfd
int eventfd(unsigned int initval, int flags);
eventfd 是Linux提供内核态的事件等待/通知机制,内核维护了一个8字节的整型数,该整型数由 initval 来初始化, flags 参数可以由以下值位或而来:
EFD_CLOEXEC:设置该描述符的 O_CLOEXEC 标志。
EFD_NONBLOCK:设置描述符为非阻塞模式。
EFD_SEMAPHORE:设置描述符为信号量工作模式,在此模式下, read 模式会使整型数减1并返回数值1。
当内核维护的8字节整型数为0时, read 操作会阻塞,如果为fd设置为非阻塞模式,则返回 EAGAIN 错误。
eventfd实现的异步队列
|
|
atomic原子变量
这种方式和第一种比较接近,只不过采用原子操作全局标志变量,没有mutex上锁,只是粗糙地满足通信队列的需求。
|
|
附录1
|
|