使用条件变量的时候需要注意什么?

目录

前言

一、条件变量中的两个关键操作

二、信号丢失问题

三、原子性问题

四、如何解决这些问题

五、示例代码(C++)


前言

在多线程编程中,条件变量(condition_variable)是用来让一个线程等待某个条件满足时被唤醒的机制。而使用条件变量时,需要特别注意其用法和一些潜在问题,尤其是 信号丢失原子性 问题。

  • 信号丢失问题:如果没有互斥锁保护,signal 可能会在 wait 之前被调用,这会导致等待线程错过信号并永远等待下去。

  • 原子性保证:通过使用互斥锁,可以确保条件判断和 wait 操作的原子性。这样,即使 signalwait 的调用顺序错乱,也不会造成线程死锁或条件变量失效的问题。

一、条件变量中的两个关键操作

  1. wait:一个线程调用 wait,表示这个线程要等待某个条件的满足。它会阻塞当前线程,直到收到其他线程的通知或条件满足。
  2. signalnotify:另一个线程在条件满足后调用 signal(或 notify),通知等待的线程条件已经满足,唤醒等待中的线程。

二、信号丢失问题

signal 发生在 wait 之前,可能会造成问题:如果 signal 先发出,而等待线程尚未进入 wait 状态,那么这个 signal 会被丢失。这是因为条件变量的 signal 只唤醒已经进入 wait 状态的线程,如果线程还没有进入 wait,信号就会丢失,导致线程永远不会醒来。

三、原子性问题

wait 过程中需要确保 条件判断和阻塞操作是原子操作,即要保证这两者之间没有其他线程插入干扰。条件变量通常与互斥锁(mutex)结合使用,以确保线程在 wait 时不会错过条件变化,也不会出现竞争条件。

四、如何解决这些问题

  1. 加锁机制:你需要使用同一把互斥锁(mutex)来保护共享资源的访问,确保 waitsignal 操作的顺序不会乱。

    • wait 之前加锁:等待条件的线程应该在调用 wait 之前先加锁,并在 wait 内释放锁,但同时阻塞等待信号。
    • signal 时加锁:通知条件满足的线程(通过 signalnotify)在发出信号时,也应该持有相同的锁,这样就能确保 waitsignal 的正确同步。
  2. 条件判断wait 通常需要在循环中使用,确保条件在每次被唤醒时都重新检查。

五、示例代码(C++)

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    
    // 等待条件满足
    while (!ready) {
        cv.wait(lock);  // wait时会释放锁,但重新被唤醒时会自动重新获取锁
    }
    
    std::cout << "Thread is working!\n";
}

void notifier() {
    std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟一些工作
    
    std::unique_lock<std::mutex> lock(mtx);  // 获取锁
    ready = true;  // 改变条件
    cv.notify_one();  // 唤醒等待线程
}

int main() {
    std::thread t1(worker);  // 启动等待线程
    std::thread t2(notifier);  // 启动通知线程
    
    t1.join();
    t2.join();
    
    return 0;
}

解释

  1. worker 线程会等待 ready 变量变为 true,通过调用 cv.wait(lock) 来释放锁并等待通知。如果 ready 还没准备好,线程会阻塞。
  2. notifier 线程会在 1 秒后改变 ready 的值并通过 notify_one() 通知等待中的线程。
  3. 因为 wait 操作在 worker 中是与互斥锁结合使用的,所以不会有信号丢失的问题。即使 notifierworker 还未等待时发送信号,worker 也会正确等待。

注意点

  • 锁的作用:锁保护了 ready 这个共享资源,使得 workernotifier 线程之间的通信是安全的,不会出现竞争条件。
  • 条件变量的循环:因为可能存在虚假唤醒(spurious wakeup),即线程被唤醒时条件可能仍然不满足,因此在 wait 返回后应当重新检查条件,这也是为什么常常将 wait 放在循环中的原因。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值