互斥锁-条件变量-信号量总结

在多线程或者多进程编程中,都需要同步机制。有名信号量可以实现进程之间的同步,线程之间同步机制选择比较多,常用的有三种,互斥锁,条件变量和无名信号量。另外,c++还提供了一些原子操作的变量atomic,也可以帮助线程之间同步。对于互斥锁和条件变量,linux系统提供了一套pthread mutex和pthread condition机制(POSIX Thread),c++标准库也提供了一套std的mutex和condition类,当然,标准库的底层实现也是基于系统提供的系统调用。使用的时候尽量使用std提供的类,接口会比较简单。
下面依次总结如下:
一.互斥锁:mutex
1.linux系统调用
a.静态分配的mutex

#include<pthread.h>
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;//默认属性, 不需要destroy。
//both return 0 success, or a positive error number on error
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//linux还提供了上面两个函数的变体,使用的比较少。
/*如果该mutex已经被lock,则会失败,并返回EBUSY错误*/
int pthread_mutex_trylock(pthread_mutex_t *mutex)
#include <time.h>
/*如果此时mutex被lock,该mutex会等待指定的时间,如果指定的时间內不能获得该锁,则返回ETIMEDOUT错误*/
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
           const struct timespec *restrict_abstime)

b.动态分配的mutex
对于动态分配于堆中的mutex,或者栈中分配的自动变量,或者经由静态分配且不使用默认属性的mutex,需用使用如下初始化接口:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_mutexattre_t *attr);
/*init 过的mutex使用完后需要destroy。destroy之后的mutex,还可以重新init使用*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);

c.mutex的属性
I : PTHREAD_MUTEX_NORMAL//默认
II : PTHREAD_MUTEX_ERRORCHECK
error check类型会对三种情况进行检测,遇到这三种类型,返回错误。这种锁比较慢,一般用来调试使用,可以找出哪里的锁出了问题,产品中不建议使用。
三种情况为:同一线程对同一mutex加锁两次, 线程对不为自己所有的mutex解锁,线程对一尚未锁定的mutex解锁。
III : PTHREAD_MUTEX_RECURSIVE
同一线程可以多次lock,每次lock内部计数会+1。unlock的时候需要同样的次数才能解锁。
设置mutex属性实例:

pthread_mutex_t mtx;
pthread_mutexattr_t mtx_attr;
int s, type;
s = pthread_mutexattr_init(&mtx_attr);
if (s != 0) {
  ERROR("pthread mutexattr init error");
}
s = pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_ERRORCHECK);
if (s != 0) {
  ERROR("pthread mutexattr settype error.");
s = pthread_mutex_init(&mtx, &mtx_attr);
if (s != 0)
  ERROR("pthread mutex init error.");
s = pthread_mutexattr_destroy(&mtx_attr);
if (s != 0)
  ERROR("pthread mutex attr destroy error");
/*do some thing*/
s = pthread_mutex_destroy(&mtx);
if (s != 0)
  ERROR("pthread mutex destroy error");

2.std标准库提供的类(参考c++标准库P989)
c++ 标准库提供了以下的mutex class。
#include <mutex>
class std::mutex
class std::recursive_mutex 允许同一线程多次锁定。解锁则需要最后一个unlock之后其他线程才能获取这个锁。
class std::timed_mutex//额外允许你传递一个时间段或者时间点,用来定义多长时间內它可以尝试捕捉一个lock。为此额外提供了两个接口try_lock_for(duration)和try_lock_until(tp)
class std::recursive_timed_mutex
操作:
mutex m;
m.~mutex()//销毁mutex
lock()
try_lock()//锁定成功返回true
unlock()
try_lock_for(duration)//尝试在时间段dur內锁定,如果锁定成功,返回true
try_lock_until(tp)//尝试在时间点tp之前锁定,如果锁定成功,返回true
同时,为了能自动unlock,标准库还提供了class std::lock_guard 模板类
值得注意的是,lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁

std::mutex val_mutex;
std::lock_guard<std::mutex> lg(val_mutex);//lock and automatically unlock.
/*比如我们可以如下定义一个宏##的意思参考http://blog.csdn.net/hellokandy/article/details/50592971)*/
#define AUTO_MEM_LOCK(mutex) \
  std::lock_guard<mutex> __mutex_lock__##mutexlock(mutex)

try_lock()的示例:

std::mutex m;
while(!m.try_lock()) {
  do_some_other_thing();
}
//adopt_lock意思是将已经锁定的mutex过继给lock_guard,确保lock被释放。
std::lock_guard<std::mutex> lg(m,std::adopt_lock);

锁住多个lock

std::mutex m1;
std::mutex m2;
{
  std::lock(m1,m2);//lock both mutexes.直到两个mutex都锁定才返回
  std::lock_guard<std::mutex> lockm1(m1, std::adopt_lock);
  std::lock_guard<std::mutex> lockm2(m2, std::adopt_lock);
}

unique_lock :
c++标准库还提供了class unique_lock<> 模板类。和lock_guard的功能非常相似,但其独有的优点为:
如果析构时,mutex仍被锁住,其析构函数自动解锁,如果没有被锁住,则析构函数不做任何事情。比lock_guard多了三个构造函数:std::try_to_lock std::chrono::seconds(1) std::defer_lock。所以一般推荐使用unique_lock,功能比较全。

//std::try_to_lock 表示企图锁定mutex,但是不希望阻塞。
std::unique_lock<std::mutex> lock(mutex, std::try_to_lock);
if(lock)//if lock was successful,检查关联的mutex是否被锁定
  do_some_thing()
//传递一个时间段或时间点给构造函数,表示尝试在一个明确的时间周期內锁定(时间长度或者某个时间点)
std::unique_lock<std::mutex> lock(mutex, std::chrono::seconds(1));//1s內锁定
std::unique_lock<std::mutex> lock(mutex, std::chrono::milliseconds(1));//1ms內锁定
// std::defer_lock表示初始化这个lock object,但尚未打算锁住它。
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
do_some_thing();
lock.lock();//开始锁定
do_some_thing()
lock.unlock();//开锁
//unique_lock离开作用域也会自动unlock。另外,还提供了release()函数用来释放mutex或是将mutex拥有权转移给另一个lock。

二.条件变量:condition:
条件变量condition允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程在共享变量没有变化的时候等待。condition必须配合mutex使用。
条件变量不保存状态信息,signal时如果没有线程在等待,则会丢失该signal,如果signal之后再有线程wait,则需要等待下一个signal才能唤醒。
1。linux提供的系统调用
a.静态分配的条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

#include <pthread.h>
/*All return 0 on success, or a positive error number on error*/
int pthread_cond_signal(pthread_cond_t *cond);//保证唤醒一个线程的wait
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有线程的wait
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)//等待条件变量的signal or broadcast。
/*如果超时没有被唤醒,则返回ETIMEOUT错误*/
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

b.动态分配的条件变量
对于动态分配于堆中的condition,或者栈中分配的自动变量,或者经由静态分配且不使用默认属性的condition,需用使用如下初始化接口:

#include<pthread.h>
/*return 0 on success, or a positive error number on error*/
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);//静态分配的condition不需要destroy。

下面提供一个生产者-消费者实例:

//生产者
s = pthread_mutex_lock(&mtx);
if (s != 0) 
  ERROR("pthread mutex lock error");
avail ++;
s = pthread_mutex_unlock(&mtx);
if (s != 0) 
  ERROR("pthread mutex unlock error");
s = pthread_cond_signal(&cond);
if (s != 0) 
  ERROR("pthread cond signal error");
//消费者
for(;;)
{
  s = pthread_mutex_lock(&mtx);
  if (s != 0) 
    ERROR("pthread mutex lock error");
  while(avail == 0) {
    s = pthread_cond_wait(&cond, &mtx);//该函数内部会unlock,被唤醒后重新lock。
    if (s != 0) 
      ERROR("pthread cond wait error");
  }
  while(avail > 0) {
    /*do something*/
    avail --;
  }
  s = pthread_mutex_unlock(&mtx);
  if (s != 0) 
   ERROR("pthread mutex unlock error");
}

2。std标准库提供的类

#include <condition_variable>
#include <mutex> //使用condition的时候必须配合使用mutex
std::mutex ready_mutex;
std::condition_variable ready_cond;
//激发的线程调用,在mutex保护之外。
read_cond.notify_one();//notify one of the waiting threads
read_cond.notify_all();//notify all the waiting threads
//等待的线程
std::unique_lock<std::mutex> lk(ready_mtuex);//不能使用lock_guard.因为该lock不确定是否被锁定。
while(!ready_flag) {
  read_cond.wait(lk);//该wait可能被假唤醒,所以被唤醒后需要检查等待的变量是否达到指定的条件。所以wait函数一般需要一个while循环。
}
还可以使用lambda表达式来达到同样的效果
std::unique_lock<std::mutex> lk(ready_mutex);
ready_cond.wait(lk, []{return ready_flag;});//每次被唤醒会检查ready_flag,直到返回true才真正返回。

condition的接口:

conditon_variable cv;
cv.notify_one();
cv.notify_all();
cv.wait(lk);
cv.wait(lk, pred)//直到等到pred结果为true才真正返回。
cv.wait_for(lk, duration);//返回枚举类std::cv_status::timeout和std::cv_status_no_timeout
cv.wait_for(lk, duration, pred);//同上面的wait
cv.wait_until(lk, timepoint);
cv.wait_until(lk, timepoint,pred);
notify_all_at_thread_exit(cv,lk);//用于线程exit时notify所有的wait。

三.atomic 变量
atomic提供一种比较简单的同步机制,因为即使对bool赋值,也不能保证是原子的,所以提供了atomic模板类来提供多种变量的一种atomic机制。因为为模板类,所以可以对bool, int type, ptr type使用。

#include <atomic>
std::atomic<bool> ready_flag(false);
ready_flag.store(true);// 相当于 (ready_flag = true;)
ready_flag.load();//取当前值。
std::atomic<int> int_flag;
++ int_flag;
int_flag = 5;
int_flag &= 0x01;
int_flag |= 0x01;
int_flag ^= 0x01;

另外,为了兼容c语言,还提供了一些c接口,比如atomic_bool等。可以参考《c++标准库》P1019.
四.信号量: semaphore:
信号量一般用于进程间通信和同步对共享资源的访问。linux系统提供了两种信号量机制,system v信号量和posix信号量,system v信号量比较复杂,现在一般使用posix信号量。标准库没有提供信号量的机制封装。信号量又分为有名信号量(基于文件)和未命名信号量(基于内存)。有名信号量可以实现进程之间的通信,而未命名信号量不能跨进程,除非在两个进程的共享内存中。posix信号量是一个整数,其值不能小于0。
1.命名信号量:

#include <fcntl.h> //defines o_* flags
#include <sys/stat.h>//defines mode 
#include <semaphore.h>
/*创建和打开semaphore,成功返回指针,失败返回SEM_FAILED。 oflag同文件的打开flag一样,如果为0,则访问一个既有信号量,如果指定了O_CREAT,并且与给定的name对应的信号量不存在,则会创建一个新信号量。如果同时指定了O_CREAT和O_EXCL,同时与给定的name的信号量已经存在,则返回错误。mode规定访问权限*/
sem_t *sem_open(const char *name, int oflag, .../*mode_t mode, unsigned int value*/);
/*return 0 on success, or -1 on error*/
int sem_close(sem_t *sem);//关闭信号量
int sem_unlink(const char *name);//删除信号量。return 0 on success, or -1 on error.
/*wait会递减(减1)sem引用的信号量的值。如果当前值大于0,则立即返回,如果等于0,则会wait。如果被信号处理器中断,则会返回失败,error_number为EINTR.*/
int sem_wait(sem_t *sem);
/*sem_wait的非阻塞版本,如果信号量value值等于0,不会阻塞,会返回EAGAIN错误。*/
int sem_try_wait(sem_t *sem);//return 0 on success, or -1 on error.
/*超时返回ETIMEDOUT错误,需要定义如下宏。*/
#define _XOPEN_SOURCE 600
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//return 0 on success, or -1 on error.
/*发布一个信号量(增加1)*/
int sem_post(sem_t *sem);//return 0 on success, or -1 on error.
/*获取当前信号量的值*/
int set_getvalue(sem_t *sem, int *sval);

2.未命名信号量:
未命名的信号量也被称为基于内存的信号量。它的sem_post, sem_wait, sem_getvalue等函数和有名信号量一样,但额外提供了另外两个函数来初始化和销毁信号量

#include <semaphore.h>
/*使用value中指定的值来对sem指向的未命名信号量进行初始化。pshared如果为0,则在线程间共享,如果不等于0,则在进程间共享,此时,该sem必须位于两个进程的共享内存中。*/
int sem_init(sem_t *sem, int pshared, unsigned int value);//return 0 on success, or -1 on error.
sem_t sem;
sem_init(&sem, 0, 0);
int sem_destroy(sem_t *sem);//return 0 on success, or -1 on error.
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 互斥:对于一个写操作,可以使用互斥来阻止其他线程读取同一个变量或者资源。读写:如果多个线程同时访问一个变量或资源,可以使用读写来保证线程安全。条件变量:当某线程需要等待另一个线程完成某任务时,可以使用条件变量来实现。信号:可以使用信号来控制同时访问的线程数,从而保证线程安全。屏障:当多个线程需要在某个时刻同步执行时,可以使用屏障来阻止其他线程继续执行,直到所有线程都到达屏障。 ### 回答2: 互斥、读写条件变量信号、屏障是操作系统中常用的同步原语,用于多线程或多进程之间的协调和同步。下面举几个例子来说明它们的应用: 1. 互斥互斥是最常见且基础的同步原语,用来保护临界区,确保同一时间只有一个线程可以访问共享资源。例如,在多线程编程中,多个线程需要访问共享的全局变量,我们可以使用互斥来保证线程的互斥访问。 2. 读写: 读写是一种特殊的,它分为读和写。多个线程可以同时获取读,但只有一个线程可以获取写。读写适用于读多写少的场景,可以提高读操作的并发性。例如,在一个文件缓存系统中,多个线程可以同时读取缓存的文件内容,但只有一个线程可以写入缓存。 3. 条件变量条件变量用于线程间的等待和通知机制,能够在满足特定条件时唤醒等待的线程。例如,在生产者-消费者模型中,生产者需要等待缓冲区不满时才能继续生产,消费者需要等待缓冲区不空时才能继续消费。条件变量可以通过等待和通知的方式实现线程的同步。 4. 信号信号用于控制对临界资源的访问数。可以将信号看作是一个计数器,当资源被占用时,计数器减1,当资源被释放时,计数器加1。例如,在操作系统中,可以使用信号来限制一个资源的并发访问数,比如限制同时访问数据库的连接数。 5. 屏障: 屏障用于控制多个线程在某个点上的同步,即在该点前的线程必须等待所有线程都到达该点才能继续执行。例如,在一个并行计算任务中,可能需要多个线程在某个阶段完成计算后再进入下一个阶段,这时可以使用屏障来同步各个线程的执行。 ### 回答3: 互斥是一种保护共享资源的,在同一时刻只允许一个线程对共享资源进行操作。例如,当多个线程同时访问一个共享计数器时,互斥可以保证每次只有一个线程能够增加或减少计数器的值,避免了竞态条件的发生。 读写是一种更高级别的,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。例如,在一个并发读写文件的场景中,读写可以保证多个线程可以同时读取文件的内容,但只允许一个线程进行写入操作。 条件变量是一种用于线程间通信的机制,可以通知等待的线程某个特定的条件已经满足。例如,在一个生产者-消费者模型中,当缓冲区满时,生产者线程可以通过条件变量通知消费者线程可以消费数据了。 信号是一种用于控制多个线程并发访问共享资源的机制。例如,当有限数的资源需要在多个线程间共享时,可以使用信号来限制资源的并发访问数,从而避免资源的过度竞争。 屏障用于线程同步,可以让多个线程在某个特定的点上等待,直到所有线程都到达该点才能继续执行。例如,当多个线程并行执行任务,但需要等待所有线程都完成自己的任务后再进行下一步操作时,可以使用屏障来实现线程间的同步。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值