关于c++:使用条件变量的那些坑你知道吗

38次阅读

共计 3024 个字符,预计需要花费 8 分钟才能阅读完成。

【工夫治理的实质是目标治理,如果咱们想要更好的利用工夫,最先要做的是找到最值得咱们破费工夫的事件,自我学习和精进才是最值得咱们做的有意义的事。】

想必大家开发过程中都会用到多线程,用到多线程基本上都会用到条件变量,你了解的条件变量只是简略的 wait 和 notify 吗,最近工作中看共事也都只是简略的应用 wait 和 notify,导致我的项目呈现 bug 却不知如何 fix bug,其实这外面还是有一些坑的,程序喵这里总结给大家。

本文内容简介:

  • 什么是条件变量?
  • 条件变量如何应用?
  • 如何解决条件变量的信号失落问题?
  • 如何解决条件变量的虚伪唤醒问题?
  • 条件变量为什么肯定要和锁配合应用?

什么是条件变量

条件变量是多线程程序中用来实现期待和唤醒逻辑罕用的办法。通常有 wait 和 notify 两个动作,wait 用于阻塞挂起线程 A,直到另一个线程 B 通过通过 notify 唤醒线程 A,唤醒后线程 A 会持续运行。
条件变量在多线程中很罕用,在有名的生产者和消费者问题中,消费者如何晓得生成者是否生产出了能够生产的产品,通过 while 循环不停的去判断是否有可生产的产品?家喻户晓,死循环极其耗费 CPU 性能,所以须要应用条件变量来阻塞线程,升高 CPU 占用率。

条件变量的应用

拿生产者和消费者问题举例,看上面这段代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consume() {std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock);
    std::cout << "consume" << vec.size() << "\n";}

void Produce() {std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {std::thread t(Consume);
    t.detach();
    Produce();
    return 0;
}

本意是消费者线程阻塞,期待生产者生产数据后去告诉消费者线程,这样消费者线程就能够拿到数据去生产。
但这里有个问题:如果先执行的 Produce(),后执行的 Consume(),生产者提前生产出了数据,去告诉消费者,然而此时消费者线程如果还没有执行到 wait 语句,即线程还没有处于挂起期待状态,线程没有期待此条件变量上,那告诉的信号就失落了,前面 Consume()中才执行 wait 处于期待状态,但此时生产者曾经不会再触发 notify,那消费者线程就会始终阻塞上来,呈现 bug
如何解决这个问题呢?能够附加一个判断条件,就能够解决这种 信号失落 问题,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {std::unique_lock<std::mutex> lock(mutex);
    if (vec.empty()) { // 退出此判断条件
        cv.wait(lock);
    }
    std::cout << "consumer" << vec.size() << "\n";}

void Produce() {std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

通过减少附加条件能够解决信号失落的问题,但这里还有个中央须要留神,消费者线程处于 wait 阻塞状态时,即便没有调用 notify,操作系统也会有一些概率会唤醒处于阻塞的线程,使其继续执行上来,这就是 虚伪唤醒 问题,当呈现了虚伪唤醒后,消费者线程继续执行,还是没有能够生产的数据,呈现了 bug。
那怎么解决虚伪唤醒的问题呢,能够在线程由阻塞状态被唤醒后持续判断附加条件,看是否满足唤醒的条件,如果满足则继续执行,如果不满足,则持续去期待,体现在代码中,行将 if 判断改为 while 循环判断,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {std::unique_lock<std::mutex> lock(mutex);
    while (vec.empty()) { // 将 if 改为 while
        cv.wait(lock);
    }
    std::cout << "consumer" << vec.size() << "\n";}

void Produce() {std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

看到这里您应该曾经明确条件变量的应用啦,须要应用 while 循环附加判断条件来解决条件变量的信号失落和虚伪唤醒问题。

有没有更简略的形式

难道咱们每次都须要应用 while 循环和附加条件来操作条件变量吗,这岂不是很麻烦,在 C ++ 中其实有更好的封装,只须要调用 wait 函数时在参数中间接增加附加条件即可,外部曾经做好了 while 循环判断,间接应用即可,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock, [&](){ return !vec.empty(); }); // 这里能够间接应用 C ++ 的封装
    std::cout << "consumer" << vec.size() << "\n";}

void Produce() {std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

但在 C 语言中就没方法啦,大家只能本人做一层封装啦。

为什么条件变量须要和锁配合应用

为什么叫条件变量呢?因为外部是通过判断及批改某个全局变量来决定线程的阻塞与唤醒,多线程操作同一个变量必定须要加锁来使得线程平安。同时一个简略的 wait 函数调用外部会很简单的,有可能线程 A 调用了 wait 函数然而还没有进入到 wait 阻塞期待前,另一个线程 B 在此时却调用了 notify 函数,此时 nofity 的信号就失落啦,如果加了锁,线程 B 必须期待线程 A 开释了锁并进入了期待状态后才能够调用 notify,继而避免信号失落。

对于条件变量就介绍到这里,心愿大家能有所播种,平时应用过程中能够避掉条件变量的坑。
更多文章,请关注我的 V X 公 主 号:程序喵小孩儿,欢送交换。

正文完
 0