多线程同步中的问题
多线程环境中,常有这样一种同步情况:一个线程等待其他所有线程完成指定工作。
比如,在开启一个新线程后,虽然已经启动线程函数,很可能还有一些准备工作需要完成(如更新当前线程id,名称等信息),这样,调用线程(创建线程的线程)并不能马上投入工作,立即向新线程传递数据,可能造成未定义行为,如调用线程的某段代码依赖于子线程id。
我们的第一反应,是使用屏障,C++ 20的std::barrier, 或者std::latch。在C++ 20以前,我们只能根据NPTL提供的API,自行封装。
muduo通过对互斥锁MutexLock + 条件变量Condition的封装,实现CoundDownLatch,进行向下计数,实现线程同步。
CountDownLatch
CountDownLatch 也被称为门阀 、 计数器 或者 闭锁。用于多个线程之间的同步,特别是一个线程等待另一个或多个线程。
CountDownLatch内部持有一个向下计数的计数器count_,构造时给定一个初值,代表需要等待的线程数。每个线程完成一个任务,count_减1,当count_值减到0时,代表所有线程已经完成了所有任务,在CountDownLatch上等待的线程就可以继续执行了。
CountDownLatch的接口
需要等待其他线程完成任务的线程,调用wait(),等待count_变为0;
任务线程,如果完成了任务,就调用countDown(),将count_计数值-1,值减到0时,会唤醒所有等待线程继续执行;
/**
* Count down from a count user specified to zero.
*
* Thread safe.
*/
class CountDownLatch
{
public:
explicit CountDownLatch(int count);
/**
* Wait until count_ decrease to 0
*/
void wait();
/**
* Decrease 1 in count_
*/
void countDown();
/**
* Return the value of count_
*/
int getCount() const;
private:
mutable MutexLock mutex_;
Condition cond_ GUARDED_BY(mutex_);
int count_ GUARDED_BY(mutex_);
};
CountDownLatch的实现
通过mutex_,确保所有对count_的操作,都是线程安全的。
CountDownLatch::CountDownLatch(int count)
: mutex_(),
cond_(mutex_),
count_(count)
{
}
void CountDownLatch::wait()
{
MutexLockGuard lock(mutex_);
while (count_ > 0)
{
cond_.wait();
}
}
/**
* Count down count_ from init value to 0, then notify thread
* waiting on the condition cond_.
*/
void CountDownLatch::countDown()
{
MutexLockGuard lock(mutex_);
--count_;
if (count_ == 0)
{
cond_.notifyAll();
}
}
int CountDownLatch::getCount() const {
MutexLockGuard lock(mutex_);
return count_;
}
barrier实现线程同步
利用NPTL提供的屏障pthread_barrier实现线程同步。
pthread_barrier的几个接口:
#include <pthread.h>
int pthread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr, unsigned count);
int pthread_barrier_wait(pthread_barrier_t *barrier);
pthread_barrier_init 用于对屏障初始化,pthread_barrier_destroy 用于反初始化。
初始化时,用count指定要等待的线程数目。attr指定屏障对象的属性,值为NULL表示默认属性初始化屏障。
调用pthread_barrier_wait,表明线程完成了工作,准备等待所有其他线程赶上来。调用pthread_barrier_wait的线程屏障计数,未达到屏障初始化设置的count初值时,会进入休眠状态。如果线程是最后一个调用pthread_barrier_wait的线程,满足屏障计数,那么所有线程将被唤醒。
static pthread_barrier_t barrier;
// thread1
void thread_func1()
{
pthread_barrier_init(&barrier, NULL, 2);
std::thread th2(thread_func2);
th2.detach();
printf("thread1 wait\n");
pthread_barrier_wait(&barrier);
sleep(1);
printf("thread1 run\n");
pthread_barrier_destroy(&barrier);
}
// thread2
void thread_func2()
{
printf("thread2 wait\n");
pthread_barrier_wait(&barrier);
printf("thread2 run\n");
}
barrier与自定义CountDownLatch区别
从屏障提供的接口特性,以及上面的示例,我们可以知道,屏障跟CountDownLatch最大的区别:等待屏障计数的所有线程都会休眠,满足条件后,所有线程都会同时继续运行;而CountDownLatch是一个线程等待另外的一个或多个线程,被等待的线程并不会休眠。
参考
https://www.apiref.com/cpp-zh/cpp/thread/latch.html
https://www.apiref.com/cpp-zh/cpp/thread/barrier.html
APUE 第3版