C/C++:互斥锁和条件变量

       互斥锁、条件变量和信号量是实现线程间同步的三种方式。在多线程程序访问临界资源时,可以对各个线程进行访问限制,每次只允许一个线程访问临界资源。条件变量相当于是互斥锁的一种补充,是线程中的东西,就是等待某一条件的发生,和信号一样。占有临界资源的线程在执行完对于资源的操作后,会发出“信号”,然后其它未占有资源的线程在感知到“信号”后就会抢占资源。

互斥锁的使用

需要的头文件:pthread.h
1)定义并初始化互斥锁

函数原型:pthread_mutex_t mutex;
                  int  pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mattr)
参数说明:mutex互斥锁地址,mattr属性通常默认nullptr,可以用于设置互斥锁的特性。

互斥锁属性:

  • PTHREAD_MUTEX_NORMAL,在同一线程中重复锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。
  • PTHREAD_MUTEX_ERRORCHECK提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
  • PTHREAD_MUTEX_RECURSIVE一个线程可以多次锁定同一个互斥锁,互斥锁会保留一个锁定计数。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

2)互斥锁加锁

函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
函数说明:当 pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。
返回值:成功返回0。其他任何返回值都表示出现了错误。错误返回值含义如下。
EAGAIN:已超出了互斥锁递归锁定的最大次数,无法获取该互斥锁。
EDEADLK:当前线程已经拥有互斥锁。

3)互斥锁解锁

函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数说明:pthread_mutex_unlock() 释放mutex引用的互斥锁。互斥锁的释放方式取决于互斥锁的类型属性。对于 PTHREAD_MUTEX_RECURSIVE 类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。
返回值:成功返回0,错误返回值含义如下。
EPERM :当前线程不拥有互斥锁。

4)使用非阻塞互斥锁加锁

函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
函数说明:pthread_mutex_trylock() 是 pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。
返回值:成功返回0。错误返回值含义如下。
EBUSY :mutex 所指向的互斥锁已锁定,无法获取该互斥锁。
EAGAIN:超出了 mutex 的递归锁定最大次数,无法获取该互斥锁。

5)销毁互斥锁

函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:pthread_mutex_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL: mutex 指定的值不会引用已初始化的互斥锁对象。

条件变量的使用

需要的头文件:pthread.h
1)定义并初始化条件变量

函数原型:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
                  int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数说明:cond 条件变量地址,cond_attr属性通常默认nullptr,可以用于设置互斥锁的属性。

2)等待条件变量

函数原型:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
              int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
参数说明:cond 条件变量地址,mutex互斥锁地址,表示锁等待在条件变量上,abstime表示等待时限

3)唤醒等待条件变量上的线程

函数原型:int pthread_cond_signal(pthread_cond_t *cond);               //唤醒等待该条件变量上的某个线程
                  int pthread_cond_broadcast(pthread_cond_t *cond);         //唤醒等待该条件变量上的所有线程
参数说明:cond条件变量地址

4)销毁条件变量
函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
参数说明:cond条件变量地址

互斥锁与条件变量的Demo

生产者和消费者模型:

       消费者将count每次减去1,生产者将count每次加1,消费者会判断count的大小,如果count==0那么消费者线程要阻塞,但是它还会一直占有锁,所以这样就阻止了其它线程对count的操作,如果其它线程不能操作count,程序就没有意义;因此我们要用到条件变量来解决这个问题。调用pthread_cond_wait(&cond, &mutex);让互斥锁mutex在这个cond条件上等待,线程调用pthread_cond_wait这个函数之后,内核会做下面这些事:

  • 让拿到锁的线程,把锁暂时释放
  • 将线程休眠
  • 线程等待通知,收到通知后重新获取锁。

       线程库将上面三步做成了原子性操作,和Linux内核绑定在一起。在生产者线程中当对count++之后(也就是生产了产品),会通过调用pthread_cond_signal(&cond)来向条件变量cond上发送一个信号,表示条件满足。如果条件满足,那么刚才因为调用pthread_cond_wait而等待的消费者线程会醒来(重新获取锁,并再次判断条件是否满足),如果count>0,就在临界区进行操作,然后解锁并离开临界区。因为涉及到多个线程对全局变量count进行操作,所以要用线程互斥锁对count进行控制;所以首先定义互斥锁mutex,然后调用pthread_mutex_lock(&mutex)进行上锁,对count进行操作之后再调用pthread_mutex_unlock(&mutex)进行解锁。

       我们还希望对count的最大值进行控制,比如希望它的最大值是10;那么当count等于10的时候,就要等待;

// for test
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

int count = 0;
pthread_mutex_t mutex;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* produce(void* args)
{
    while(true)
    {
        sleep(1);
        pthread_mutex_lock(&mutex);
        while(count >= 10)
        {
            sleep(1);
            cout << "product too much, sleep 1 second." << endl;
        }
        ++count;
        cout << "producer is  " << pthread_self() << "    count is  " << count << endl;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(nullptr);
}

void* consume(void* args)
{
    while(true)
    {
        sleep(1);
        pthread_mutex_lock(&mutex);
        while(count == 0)
        {
            cout << "product too little, sleep 1 second." << endl;
            pthread_cond_wait(&cond, &mutex);
        }
        --count;
        cout << "consumer is  " << pthread_self() << "    count is  " << count << endl;
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(nullptr);
}

int main()
{
    pthread_t threadID_1 = 0;
    pthread_t threadID_2 = 0;
	
    const int ret1 = pthread_create(&threadID_1, 0, &produce, nullptr);
    const int ret2 = pthread_create(&threadID_2, 0, &consume, nullptr);
	
    if(ret1 == -1 || ret2 == -1)
    {
        return 0;
    }
    pthread_detach(threadID_1);
    pthread_detach(threadID_2);
    getchar();
    return 0;
}

 

展开阅读全文

没有更多推荐了,返回首页