【Linux —— 线程同步 - 条件变量】

条件变量的概念

条件变量是一种用于线程间同步的机制,主要用于协调线程之间的执行顺序,允许线程在某个条件不满足时进入等待状态,直到其他线程通知条件已满足。条件变量通常与互斥锁一起使用,以确保在检查和修改共享状态时的安全性。

主要功能:

  • 线程等待:当某个条件不满足时,线程可以在条件变量上等待。
  • 线程通知:当条件满足时,其他线程可以通知等待的线程继续执行。

条件变量的核心作用是提供一种机制,使得线程可以在条件未满足时挂起自己,并在条件满足时被唤醒。
这样可以避免忙等待(busy waiting)和不必要的 CPU 资源浪费。

互斥量与条件变量的关系

  互斥量和条件变量在多线程编程中密切相关,通常结合使用以实现更加复杂的同步机制。他们之间的关系可以概括为一下几点:

  1. 互斥量保护条件变量
     条件变量的操作必须在互斥量的保护之下进行,以确保对共享资源的安全访问。线程在调用pthread_cond_wait()等待条件变量时,必须先获取相关的互斥变量锁。这样可以防止其他线程在条件检查和修改期间访问共享资源。(对共享区资源的检查也是一种访问)

  2. 等待与通知机制
     当线程调用pthread_cond_wait()来进入等待状态时,互斥量会被自动解锁,允许其他线程修改共享资源和条件状态。其他线程在改变条件后,会使用pthread_cond_signal()pthread_cond_broadcast() 来通知等待的线程,这需要在持有互斥量的情况下使用。

  3. 重新检查条件
     被唤醒的线程在继续执行之前,必须重新获取互斥量并检查条件是否满足。这是因为在它被唤醒
    之前,其他线程可能已经改变了条件状态。线程不能假设条件一定满足,而是需要重新验证。

  4. 互斥量的状态影响条件变量的使用
     互斥量的状态(锁定或解锁)直接影响到线程是否能够在条件变量上等待或被唤醒。只有在互斥
    量被解锁的情况下,其他线程才能访问和修改条件变量的相关条件。

  5. 组合使用
     互斥量和条件变量通常一起使用来解决复杂的同步问题,例如生产者-消费者问题、读者-写者问
    题等。它们的组合使用能够有效地协调线程之间的执行顺序,避免竞争条件和资源浪费。

总之,互斥量和条件变量是多线程编程中不可或缺的同步机制。互斥量确保同一时刻只有一个线
程可以访问共享资源,而条件变量则提供了一种线程等待和通知的机制。通过将这两种机制结合
使用,程序员可以编写出更加复杂和高效的多线程程序。

关键点总结:


  • 互斥量的自动解锁:当线程在条件变量上等待时,互斥量会被自动解锁,这使得其他线程可以修改共享资源。
  • 条件的重新检查:被唤醒的线程在继续执行之前必须重新获取互斥量并检查条件,以确保条件的有效性。
  • 互斥量的状态影响条件变量的使用:互斥量的状态(锁定或解锁)直接影响到线程是否能够在条件变量上等待或被唤醒。

条件变量的操作

条件变量的操作主要包括:

  • 等待(Wait):当线程发现条件不满足时,它会调用 pthread_cond_wait() 函数进入等待状态。在调用该函数时,线程会自动释放与条件变量关联的互斥量,并进入阻塞状态,等待其他线程的通知。
  • 通知(Signal):当条件满足时,其他线程可以调用pthread_cond_signal() pthread_cond_broadcast() 来唤醒一个或多个等待该条件变量的线程。

1.条件变量的初始化:

函数原型:

int pthread_cond_init(pthread_cond_t *restrict cond, 
                                      const pthread_condattr_t *restrict attr);

参数

  • cond: 指向要初始化的条件变量的指针。
  • attr: 指向条件变量属性对象的指针。如果设置为 NULL,则使用默认属性。

返回值

  • 成功时返回 0。
  • 失败时返回错误编号,例如:
    • EINVAL: 参数无效。
    • ENOMEM: 内存不足。

2.条件变量的销毁

函数原型:

int pthread_cond_destroy(pthread_cond_t *cond);

参数

  • cond: 指向要销毁的条件变量的指针。

返回值

  • 成功时返回 0。
  • 失败时返回错误编号,例如:
    • EBUSY: 条件变量上仍有线程在等待。
    • EINVAL: 参数无效。
  1. 等待条件变量

函数原型:

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

参数

  • cond: 指向条件变量的指针。
  • mutex: 指向与条件变量关联的互斥量的指针。
    返回值
  • 成功时返回 0。
  • 失败时返回错误编号,例如:
    • EINVAL: 参数无效。
    • ESRCH: 线程不存在。

为什么要在pthread_cond_wait中传入互斥锁?


pthread_cond_wait() 中传入互斥锁是为了确保线程安全。当线程调用该函数时,互斥锁会被自动解锁,允许其他线程访问和修改共享资源。线程在等待条件变量时,必须持有互斥锁,以防止竞争条件,并在被唤醒后重新获取互斥锁,以确保对共享资源的安全访问。

  1. 唤醒条件变量

函数原型:

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

参数

  • cond: 指向条件变量的指针。

返回值

  • 成功时返回 0。
  • 失败时返回错误编号,例如:
    • EINVAL: 参数无效。

使用说明:
pthread_cond_signal():唤醒一个等待该条件变量的线程。如果没有线程在等待,则不执行任何操作。 pthread_cond_broadcast():唤醒所有等待该条件变量的线程。

代码示例

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

const int num = 2;
int n = 4;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;

void* Wait(void* args)
{
    std::string name = *static_cast<std::string*>(args);
    delete static_cast<std::string*>(args); // 释放动态分配的内存

    int m = 2;

    while (m--)
    {
        pthread_mutex_lock(&g_mutex);
        pthread_cond_wait(&g_cond, &g_mutex);
        std::cout << "I am: " << name << " ..." << std::endl;
        pthread_mutex_unlock(&g_mutex);
    }

    return nullptr;
}

int main()
{
    pthread_t threads[num];

    for (int i = 0; i < num; i++)
    {
        auto name = new std::string("thread-" + std::to_string(i + 1));
        if (pthread_create(&threads[i], nullptr, Wait, name) != 0) {
            std::cerr << "Failed to create thread " << i + 1 << std::endl;
            delete name; // 确保内存被释放
            return 1;
        }
    }

    sleep(1);

    while (n--)
    {
        pthread_mutex_lock(&g_mutex); // 锁住互斥量
        pthread_cond_signal(&g_cond);
        std::cout << "唤醒了一个线程" << std::endl;
        pthread_mutex_unlock(&g_mutex); // 解锁互斥量
        sleep(2);
    }

    for (int i = 0; i < num; i++)
    {
        pthread_join(threads[i], nullptr);
    }

    // 销毁互斥量和条件变量
    pthread_mutex_destroy(&g_mutex);
    pthread_cond_destroy(&g_cond);

    return 0;
}

在这里插入图片描述

这段代码实现了一个简单的多线程程序,其中创建了两个线程,它们在条件变量上等待,直到主线程发出信号唤醒它们。代码使用了互斥量来确保对共享资源的安全访问,并使用条件变量来实现线程之间的同步

  1. 在 main 函数中,首先创建了两个线程,并指定它们要执行的函数为 Wait。每个线程都有一个唯一的名称,用于标识它们。

  2. 创建线程时,使用了 new 动态分配内存来存储线程名称。主线程在创建所有线程后睡眠 1 秒钟,以确保所有线程都已创建并处于等待状态。

  3. 然后,主线程进入循环,共执行 4 次。在每次循环中,它都会调用pthread_cond_signal函数来唤醒一个等待的线程。在唤醒线程之后,主线程会打印一条消息,表示已经唤醒了一个线程,并睡眠 2 秒钟。

  4. 在主线程唤醒所有线程之后,它会等待所有线程完成执行。这是通过调用 pthread_join 函数来实现的。当所有线程都完成执行后,main 函数返回 0,表示程序成功退出。

  5. Wait 函数中,每个线程都会尝试获取互斥量 g_mutex 。一旦获取到互斥量,线程就会调用 pthread_cond_wait 函数在条件变量 g_cond 上等待。 这里是引用
    这里的关键点是:

    1. 获取互斥量:
      • 在进入 while 循环之前,线程会调用 pthread_mutex_lock(&g_mutex) 来获取互斥量g_mutex
      • 这确保了在访问共享资源和条件变量时,只有一个线程可以进入临界区。
    2. 等待条件变量:
      • 获取互斥量后,线程会立即调用 pthread_cond_wait(&g_cond, &g_mutex)
      • pthread_cond_wait() 函数有两个参数:条件变量 g_cond 和互斥量 g_mutex
      • 当线程调用这个函数时,会发生以下事情:
        • 线程会自动释放互斥量 g_mutex。这允许其他线程在此期间获取互斥量并进入临界区。
        • 线程会被放入条件变量 g_cond 的等待队列,并进入阻塞状态。
    3. 被唤醒后:
      • 当其他线程调用 pthread_cond_signal()pthread_cond_broadcast()来唤醒等待的线程时,被唤醒的线程会重新获取互斥量 g_mutex
      • 这确保了在继续执行之前,线程可以安全地访问共享资源。

    所以,在 Wait 函数中,每个线程首先获取互斥量,然后调用 pthread_cond_wait()在条件变量上等待。这个过程确保了线程在访问共享资源之前拥有互斥量,并且只有在条件满足时才会继续执行。

  6. 在调用pthread_cond_wait时,线程会自动释放互斥量 g_mutex。当线程被唤醒时,它会重新获取互斥量 g_mutex。一旦获取到互斥量,线程就会打印一条消息,表示它已经被唤醒。在打印消息之后,线程会释放互斥量 g_mutex。这个过程会重复两次。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值