并发编程有两种基本模型,一种是消息传递,另一种是共享内存。在分布式系统中,运行在多台机器上的多个进程的并行编程只有消息传递。在多线程编程中,消息传递更容易保证程序的正确性。在用C/C++编写多线程程序时,我们需要了解共享内存模型下的同步原语。
线程同步的四项原则:
1)首要原则是尽量最低限度地共享对象,减少需要同步的场合。
2)其次是使用高级的并发编程构件,如生产者-消费者队列。
3)最后不得已必须使用底层的同步原语时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。
4)除了使用atomic整数之外,不自己编写lock-free代码,也不要用“内核级”同步原语。
平常看书及看代码,接触到最多的就是底层同步原语了,linux下的同步原语包括互斥器,条件变量,读写锁,和信号量。可能互斥器和条件变量是大家用的最多的了,C++标准库中也提供了互斥器,template
<
class
Mutex>
class
lock_guard,是以模板定义的
类,lock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。
模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,是一个基本的 BasicLockable 类型,BasicLockable 类型的对象只需满足两种操作,lock 和 unlock,另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock。
在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。
条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个回合的场所。条件变量与互斥器一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件变量本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量的使用方式为,对wait端:
1)必须与mutex一起使用,受mutex保护。
2)在muetx已上锁的时候才能调用wait().
3判断布尔条件和wait()放到while循环中,是为了防止欺骗性唤醒(spurious wakeup)。
实例代码为:
struct msg{
struct msg* m_next;
};
struct msg* workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void){
struct mad *mp;
for(;;){
pthread_mutex_lock(&qlock);
while(workq == NULL)
pthread_cond_wait(&qready,&qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
}
}
对于signal/broadcast端:
1)在signal之前一般要修改布尔表达式。
2)修改布尔表达式之前通常用mutex保护。
3)区分signal与broadcast,broadcast通常用于表明状态变化,signal通常用于表示资源可用。
即获取互斥量、改变条件、释放互斥量并向条件变量发送信号。
实例代码为:
void enqueue_msg(struct msg* mp){
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
互斥器和条件变量构成了多线程编程的全部同步原语,用它们即可完成任何多线程的同步任务,二者不能互相替换。