【多线程】线程同步--条件变量的原理及其使用

时间:2024-07-11 07:08:55

文章目录

  • 前言
  • 线程同步的基本概念
    • 条件变量
      • 定义条件变量
      • 初始化条件变量
      • 销毁条件变量
      • 等待条件(重要)
      • 唤醒等待
      • 简单运用
      • 常见使用条件变量的格式

前言

线程同步意味着在多线程并发执行中,协调线程之间的执行顺序,以确保共享资源被正确访问和修改。线程同步的维护本质就是在安排线程之间的执行顺序。那么在linux中是如何维护线程同步的呢?本篇文章将围绕这个为题展开叙述。

线程同步的基本概念

下面介绍一些有关线程同步的基本概念。

条件变量

当一个线程互斥的访问某个变量,即访问临界资源时给临界区上互斥锁,这个时候其它线程只能等待。那什么时候其它线程可以继续申请临界资源呢?我们希望当一个线程使用完临界资源后,正在等待的线程能够知道这一事件的发生从而重新申请资源,而不是一直重复申请这个动作

就像一个闹钟,当闹钟响了之后我们就知道该起床了,而不是睡一下又看下时间。

条件变量提供一种线程通信的方法,使得一个线程可以等待另一个线程满足某种条件后再继续执行。具体的,我们将这种通知一个线程继续执行的动作称为唤醒

于是,借助条件变量,我们就能实现协调线程之间访问临时资源的顺序性。

同时线程库给我们提供了一些接口用来操作条件变量,下面介绍一些常见的关于条件变量的操作。(头文件都是pthread.h

定义条件变量

初始化条件变量

  1. 动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 
  • cond :要初始化的条件变量
  • attr:条件变量的属性,通常是NULL
  1. 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

销毁某个条件变量,成功返回0,失败返回错误代码
在这里插入图片描述

等待条件(重要)

如果当前线程没有申请到临界资源,该线程可以选择等待。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

在调用该函数时,互斥锁mutex必须被锁住,并等待唤醒。唤醒之后线程重新锁住互斥锁并继续执行。值得注意的是,pthread_cond_wait函数首先会解锁与之关联的互斥锁mutex,这也是为什么使用该函数时mutex必须是被锁住的。然后调用该函数的线程进入阻塞状态,直到被唤醒。最后,条件变量被通知之后,pthread_cond_wait重新锁定互斥锁mutex
对于该函数提出以下问题:

  • 为什么要在pthread_cond_wait中传入互斥锁
    • 在调用 pthread_cond_wait 时,互斥锁是已经锁住的,确保没有其他线程可以修改共享资源。
    • pthread_cond_wait 在进入等待状态之前会自动释放互斥锁,使得其他线程可以修改条件。
    • 当线程被唤醒后,pthread_cond_wait 会重新获得互斥锁,然后再继续执行,因为此时还在临界区,还会访问临界资源。

唤醒等待

  1. 唤醒某个线程
int pthread_cond_signal(pthread_cond_t *cond);

唤醒一个等待在条件变量 cond 上的线程。如果有多个线程在等待条件变量,具体唤醒哪一个线程是不确定的。
成功返回0,失败则返回错误码

  1. 唤醒所有正在等待该条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒所有等待在条件变量 cond 上的线程。同样成功返回0,失败则返回错误码。

简单运用

下面我们使用条件变量和互斥锁来设计一个简单的代码样例

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1(void *arg) // 等待函数,执行该函数的线程一直处于while (true)
{
    pthread_cond_wait(&cond, &mutex);
    cout << "被唤醒" << endl;

    return arg;
}

void *r2(void *arg) // 唤醒函数,执行该函数的线程一直尝试唤醒某个等待的线程
{

    while (true)
    {
        pthread_cond_signal(&cond);
        cout << "唤醒某个线程" << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t t1, t2; // 定义两个线程

    pthread_cond_init(&cond, NULL);   // 初始化条件变量
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁

    pthread_create(&t1, NULL, r1, NULL); // 创建线程并分配执行函数
    pthread_create(&t1, NULL, r2, NULL);

    pthread_join(t1, NULL); // 等待线程退出
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mutex); // 销毁互斥锁和条件变量
    pthread_cond_destroy(&cond);

    return 0;
}

在这里插入图片描述

常见使用条件变量的格式

  • 等待条件代码:
pthread_mutex_lock(&mutex); 
 while (条件为假) //不满足条件陷入等待,在循环中等待是为了防止伪唤醒
 	pthread_cond_wait(cond, mutex); 
// ...
// 访问临界资源
 //...
 pthread_mutex_unlock(&mutex); 
  • 给条件发送信号,即可以唤醒等待条件中的线程
pthread_mutex_lock(&mutex); 
 设置条件为真 
 pthread_cond_signal(cond); //唤醒某个线程
 pthread_mutex_unlock(&mutex);