muduo中对互斥量和条件变量的操作基本上都是调用它们对应的相关函数来实现的。例如MutexLock::lock()即调用pthread_mutex_lock(),Condition::wait()即调用pthread_cond_wait()等等
1.互斥量类MutexLock和MutexLockGuard
要点
参考https://blog.youkuaiyun.com/Leeds1993/article/details/52184922
(1)需要理解的是使用RAII(资源获取就是初始化)手法封装MutexLockGuard:不手工调用lock()和unlock()函数,一切交给栈上的Guard对象的构造和析构函数负责。
(2)互斥锁通常用于保护由多个线程或多进程分享的共享数据,一般是一些可供线程间使用的全局变量,来达到线程同步的目的,即保证任何时刻只有一个线程或进程在执行其中的代码。
(3)互斥量相关函数
互斥锁的初始化有两种初始化方式:
I.对于静态分配的互斥锁一般用宏赋值的方式初始化,
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
II.对于动态分配的互斥锁(如调用malloc)或分配在共享内存中,则必须调用pthread_mutex_init()函数来进行初始化。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); // 参数mutexattr指定互斥量的属性,NULL为默认属性
// pthread_mutex_lock和pthread_mutex_unlock都是原子操作
// 如果一个线程调用pthread_mutex_lock试图锁住互斥量,而该互斥量,又被其他线程锁住(占用)
// 则该线程的pthread_mutex_lock调用就会阻塞直到其他线程对该互斥量进行解锁,该线程才能获得该互斥量
// pthread_mutex_lock调用才会返回
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:成功时返回0,失败时返回错误代码,它们并不设置errno
(4)作者在Mutex.h代码的最后一行还定义了一个宏:
// Prevent misuse like:
// MutexLockGuard(mutex_);
// A tempory object doesn't hold the lock for long!
#define MutexLockGuard(x) error "Missing guard object name"
它的作用是防止程序里出现如下错误:
void doit()
{
MutexLockGuard(mutex); // 遗漏了变量名,产生一个临时对象又马上销毁了
// 结果没有锁住临界区
// 正确写法是 MutexLockGuard lock(mutex);
}
Mutex.h依次有两个类:MutexLock和MutexLockGuard
#ifndef MUDUO_BASE_MUTEX_H
#define MUDUO_BASE_MUTEX_H
#include <muduo/base/CurrentThread.h>
#include <boost/noncopyable.hpp>
#include <assert.h>
#include <pthread.h>
#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret); \ //__typeof__(ret): ret的数据类型, 声明和ret一样类型的errnum,
//检查括号内的ret表达式是不是为0
assert(errnum == 0); (void)errnum;
})
namespace muduo
{
class MutexLock : boost::noncopyable
{
public:
MutexLock() //拥有锁的线程id初始化0
: holder_(0)
{
MCHECK(pthread_mutex_init(&mutex_, NULL)); //检查里面的语句运行是否成功(成功的话括号内表达式会返回0)
}
~MutexLock()
{
assert(holder_ == 0); //断言这个锁没有被用,才去销毁
MCHECK(pthread_mutex_destroy(&mutex_));
}
// must be called when locked, i.e. for assertion
bool isLockedByThisThread() const //是不是当前线程拿着这把锁
{
return holder_ == CurrentThread::tid();
}
void assertLocked() const
{
assert(isLockedByThisThread());
}
// internal usage
void lock() //上锁,并且holder_值赋为持有该锁的线程
{
MCHECK(pthread_mutex_lock(&mutex_));
assignHolder();
}
void unlock() //解锁,并且holder_值赋为0
{
unassignHolder();
MCHECK(pthread_mutex_unlock(&mutex_));
}
pthread_mutex_t* getPthreadMutex() /* get mutex, non-const */
{
return &mutex_;
}
private:
//声明一个友元类,以及定义了UnassignGuard类,为何这么做?看Condition类代码
friend class Condition;
class UnassignGuard : boost::noncopyable
{
public:
UnassignGuard(MutexLock& owner)
: owner_(owner)
{
owner_.unassignHolder();
}
~UnassignGuard()
{
owner_.assignHolder();
}
private:
MutexLock& owner_;
};
void unassignHolder()
{
holder_ = 0;
}
void assignHolder()
{
holder_ = CurrentThread::tid();
}
pthread_mutex_t mutex_;
pid_t holder_;
};
class MutexLockGuard : boost::noncopyable
{
public:
explicit MutexLockGuard(MutexLock& mutex)
: mutex_(mutex)
{
mutex_.lock();
}
~MutexLockGuard()
{
mutex_.unlock(); //MutexLockGuard和MutexLock只是关联关系,(使用了MutexLock的lock和unlock)
//MutexLockGuard析构了MutexLock对象仍在
}
private:
MutexLock& mutex_;
};
}
// Prevent misuse like:
// MutexLockGuard(mutex_);
// A tempory object doesn't hold the lock for long!
#define MutexLockGuard(x) error "Missing guard object name" //不能构造临时对象
#endif // MUDUO_BASE_MUTEX_H
2.Condition类
要点
(1)条件变量相关函数
#include <pthread.h>
// pthread_cond_t类型的变量可以用PTHREAD_COND_INITIALIZER常量进行静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 初始化。当cond_attr为NULL时,使用缺省的属性
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
// 唤醒等待在相应条件变量上的一个线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在相应条件变量上的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
// 会自动解锁互斥量,等待条件变量被触发
// 在调用pthread_cond_wait之前,应用程序必须加锁互斥量
// pthread_cond_wait函数返回前,自动重新对互斥量加锁
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 允许线程就阻塞时间设置一个限制值
// 如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁且pthread_cond_timedwait返回错误ETIMEDOUT
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
// 销毁一个条件变量,释放它拥有的资源
int pthread_cond_destroy(pthread_cond_t *cond);
(2)需要注意的是:如果一个class要包含MutexLock和Condition,要注意它们的声明顺序和初始化顺序(初始化顺序要与成员声明保持一致),mutex_应先于condition_构造,并作为后者的构造参数。
(3)Contidion::waitForSeconds()用到了clock_gettime函数设置限制时间。clock_gettime函数和struct timespec结构
#include <time.h>
// 最高精度为纳秒
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // nanoseconds
};
// clock_gettime函数原型
// 可以用于计算精度和纳秒
long sys_clock_gettime (clockid_t which_clock, struct timespec *tp);
which_clock参数解释:
1.CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 00:00:00开始计时,中间时刻如果系统时间被用户该成其他,则对应的时间相应改变
2.CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
3.CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间
4.CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间
Condition.h
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)
#ifndef MUDUO_BASE_CONDITION_H
#define MUDUO_BASE_CONDITION_H
#include <muduo/base/Mutex.h>
#include <boost/noncopyable.hpp>
#include <pthread.h>
namespace muduo
{
class Condition : boost::noncopyable
{
public:
explicit Condition(MutexLock& mutex)
: mutex_(mutex)
{
MCHECK(pthread_cond_init(&pcond_, NULL));
}
~Condition()
{
MCHECK(pthread_cond_destroy(&pcond_));
}
void wait()
{ //UnassignGuard构造函数里把mutex_的拥有者pid赋值为0
MutexLock::UnassignGuard ug(mutex_); //从Mutex.h可知,UnassignGuard类是Mutex类的私有成员类,那为何这里可以访问呢?
//发现,Condition在Mutex中被声明为友元!!
MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()));
}
// returns true if time out, false otherwise.
bool waitForSeconds(int seconds); //Contidion::waitForSeconds()用到了clock_gettime函数设置限制时间。
void notify()
{
MCHECK(pthread_cond_signal(&pcond_));
}
void notifyAll() //全部通知
{
MCHECK(pthread_cond_broadcast(&pcond_));
}
private:
MutexLock& mutex_;
pthread_cond_t pcond_;
};
}
#endif // MUDUO_BASE_CONDITION_H
Condition.cc
定义waitForSeconds函数
#include <muduo/base/Condition.h>
#include <errno.h>
// returns true if time out, false otherwise.
bool muduo::Condition::waitForSeconds(int seconds)
{
struct timespec abstime;
// FIXME: use CLOCK_MONOTONIC or CLOCK_MONOTONIC_RAW to prevent time rewind.
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += seconds;
//UnassignGuard构造函数里把mutex_的拥有者pid赋值为0
MutexLock::UnassignGuard ug(mutex_);
return ETIMEDOUT == pthread_cond_timedwait(&pcond_, mutex_.getPthreadMutex(), &abstime);
}
3.利用条件变量实现的倒计时门闩类CountDownLatch.h
CountDownLatch的接口和实现很简单:
CountDownLatch.h
#ifndef MUDUO_BASE_COUNTDOWNLATCH_H
#define MUDUO_BASE_COUNTDOWNLATCH_H
#include <muduo/base/Condition.h>
#include <muduo/base/Mutex.h>
#include <boost/noncopyable.hpp>
namespace muduo
{
class CountDownLatch : boost::noncopyable
{
public:
explicit CountDownLatch(int count);
void wait();
void countDown();
int getCount() const;
private:
mutable MutexLock mutex_; //mutable: 在const成员函数也可以改变状态(getCount函数需要这个属性)
Condition condition_;
int count_;
};
}
#endif // MUDUO_BASE_COUNTDOWNLATCH_H
CountDownLatch.cc
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)
#include <muduo/base/CountDownLatch.h>
using namespace muduo;
CountDownLatch::CountDownLatch(int count)
: mutex_(),
condition_(mutex_),
count_(count)
{
}
void CountDownLatch::wait()
{
MutexLockGuard lock(mutex_);
while (count_ > 0) {
condition_.wait();
}
}
void CountDownLatch::countDown() //count-1
{
MutexLockGuard lock(mutex_);
--count_;
if (count_ == 0) {
condition_.notifyAll(); //broadcast
}
}
int CountDownLatch::getCount() const //const成员函数
{
MutexLockGuard lock(mutex_); //mutex状态要改变的,所以mutex_的属性是mutable
return count_; //count_值安全!不会改变
}
用途
倒计时是一种常用且易用的同步手段,它主要有两种用途:
(1)主线程发起多个子线程,等这些子线程各自都完成一定的任务之后,主线程才继续执行。通常用于主线程等待多个子线程完成初始化。
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>
#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <string>
#include <stdio.h>
using namespace muduo;
class Test
{
public:
Test(int numThreads)
: latch_(numThreads),
threads_(numThreads)
{
for (int i = 0; i < numThreads; ++i)
{
char name[32];
snprintf(name, sizeof name, "work thread %d", i);
threads_.push_back(new muduo::Thread(
boost::bind(&Test::threadFunc, this), muduo::string(name)));
}
for_each(threads_.begin(), threads_.end(), boost::bind(&muduo::Thread::start, _1));
}
void wait()
{
latch_.wait();
}
void joinAll()
{
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::join, _1));
}
private:
void threadFunc()
{
sleep(3);
latch_.countDown();
printf("tid=%d, %s started\n",
CurrentThread::tid(),
CurrentThread::name());
printf("tid=%d, %s stopped\n",
CurrentThread::tid(),
CurrentThread::name());
}
CountDownLatch latch_;
boost::ptr_vector<Thread> threads_;
};
/*
主线程等待子线程初始化完毕才开始工作
*/
int main()
{
printf("pid=%d, tid=%d\n", ::getpid(), CurrentThread::tid());
Test t(3);
t.wait(); //main函数线程会一直阻塞在这里,直到3个线程均latch_.countDown();
printf("pid=%d, tid=%d %s running ...\n", ::getpid(), CurrentThread::tid(), CurrentThread::name());
t.joinAll();
printf("number of created threads %d\n", Thread::numCreated());
}
(2)主线程发起多个子线程,子线程都等待主线程,主线程完成其他一些任务之后通知所有子线程开始执行。通常用于多个子线程等待主线程发出“起跑”命令。
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>
#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <string>
#include <stdio.h>
using namespace muduo;
class Test
{
public:
Test(int numThreads)
: latch_(1),
threads_(numThreads)
{
for (int i = 0; i < numThreads; ++i)
{
char name[32];
snprintf(name, sizeof name, "work thread %d", i);
threads_.push_back(new muduo::Thread( //Thread类的构造函数,传入要运行的函数指针,以及线程名字
boost::bind(&Test::threadFunc, this), muduo::string(name)));//boost::bind(&Test::threadFunc, this): this->threadFunc
}
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::start, _1));//相当于threads_[i].start()
}
void run()
{
latch_.countDown();
}
void joinAll()
{
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::join, _1));//相当于threads_[i].join()
}
private:
void threadFunc()
{
latch_.wait(); //子线程阻塞,直到主线程 latch_.countDown();
printf("tid=%d, %s started\n",
CurrentThread::tid(),
CurrentThread::name());
printf("tid=%d, %s stopped\n",
CurrentThread::tid(),
CurrentThread::name());
}
CountDownLatch latch_;
boost::ptr_vector<Thread> threads_;
};
/*
子线程等待主线程发起“起跑”
*/
int main()
{
printf("pid=%d, tid=%d\n", ::getpid(), CurrentThread::tid());
Test t(3);
sleep(3);
printf("pid=%d, tid=%d %s running ...\n", ::getpid(), CurrentThread::tid(), CurrentThread::name());
t.run(); //主线程发起起跑指令,子线程会一直阻塞直到主线程运行这句话
t.joinAll();
printf("number of created threads %d\n", Thread::numCreated());
}