muduo笔记 线程同步CountDownLatch

多线程同步中的问题

多线程环境中,常有这样一种同步情况:一个线程等待其他所有线程完成指定工作。
比如,在开启一个新线程后,虽然已经启动线程函数,很可能还有一些准备工作需要完成(如更新当前线程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是一个线程等待另外的一个或多个线程,被等待的线程并不会休眠。

参考

std::latch

std::barrier

APUE 第3版


muduo库其它部分解析参见:muduo库笔记汇总

posted @ 2022-02-28 11:22  明明1109  阅读(371)  评论(0编辑  收藏  举报