C++11多线程编程——信号量的实现

一、为何需要信号量

信号量用来干嘛的呢?搜寻答案的话,很多人都会告诉你主要用于线程同步的,意思就是线程通信的。简单来说,比如我运行了2个线程A和B,但是我希望B线程在A线程之前执行,那么我们就可以用信号量来处理。有些人可能会疑惑,那么麻烦干嘛?你不是要B线程先执行吗?那么我让A线程休眠一点时间不就可以了吗?没错,这个思路是可以的,但是如果B线程也因为某些原因(比如硬件,操作系统的原因)导致延缓执行了,这该怎么办?到底A线程该休眠多少时间合适呢?所以正确的做法就是在B线程阻塞,A线程去唤醒这个阻塞线程。

看到这儿,看过我前面文章的朋友可能一眼就看出来了这个不就是前面讲的生产消费者模型提到的用法吗?

没错,信号量的实现也是靠条件变量和互斥锁。

所以虽然C++中并没有在语言级别上支持信号量,但同样的我们可以利用以上两个来自己实现一个。

这里我也不得不提一句,条件变量和互斥锁组合使用真的非常强大,生产消费者模型中用到了,线程池中用到了,现在说的信号量也用到了,所以大家一定要好好掌握条件变量和互斥锁的使用,它们俩是你在多线程世界中纵横捭阖的利剑。

二、信号量的实现

那么我们如何用C++来实现一个信号量呢?

#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H
#include <mutex>
#include <condition_variable>
using namespace std;
 
class Semaphore
{
public:
    Semaphore(long count = 0) : count(count) {}
    //V操作,唤醒
    void signal()
    {
        unique_lock<mutex> unique(mt);
        ++count;
        if (count <= 0)
            cond.notify_one();
    }
    //P操作,阻塞
    void wait()
    {
        unique_lock<mutex> unique(mt);
        --count;
        if (count < 0)
            cond.wait(unique);
    }
    
private:
    mutex mt;
    condition_variable cond;
    long count;
};
#endif

信号量里面用到了一个叫PV操作的东西,P操作时阻塞,一般用wait()函数,V操作是唤醒,一般用singal()函数,至于不叫WS操作,反而为什么叫PV操作呢?网上说是因为提出这一系统方法的人狄克斯特拉用荷兰文定义的,因为在荷兰文中,通过叫passeren,释放叫vrijgeven,PV操作因此得名。对我们来说,这些也没有太大的意义,记住这些定义就好了,毕竟定义这种东西,是不以我们的意志为转移的。

写好了信号量的接口,那我们如何使用这个信号量呢?这个就需要我们在外部写一个多线程的调用函数来调用。

#include "semaphore.h"
#include <thread>
#include <iostream>
using namespace std;
 
Semaphore sem(0);
 
void funA()
{
    sem.wait();
    //do something
    cout << "funA" << endl;
}
 
void funB()
{
    this_thread::sleep_for(chrono::seconds(1));
    //do something
    cout << "funB" << endl;
    sem.signal();
}
 
int main()
{
    thread t1(funA);
    thread t2(funB);
    t1.join();
    t2.join();
}

 

三、信号量解析

这里我们想让funB线程运行,然后再运行funA,多线程是通过时间片轮询来执行的。

假设先开始跑funA,执行到sem.wait()的时候,进入wait函数可知,count减1,小于0,会发生阻塞,等待其他线程唤醒。

然后就会切换到funB,这里即使休眠了1秒也不会切换到funA,因为那边阻塞了,没有其他线程唤醒的话就会一直阻塞。funB休眠完之后,就会打印出结果,然后执行sem.signal(),进入signal函数可知,count加1,小于等于0,会唤醒其他阻塞的线程。

然后再切到funA,执行后面的操作,打印出结果。

所以这个就一定能保证funB先执行,funA后执行。当然前提是初始化信号量对象的时候,要初始化为0。

Semaphore sem(0);

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。像这里funB完成任务之后就通过信号量的PV操作告诉funA线程可以开始任务了。

最后需要注意的是,信号量不仅可以用于进程也可用于线程,它比条件变量要复杂很多,条件变量仅限于线程内使用,至于进程间如何使用信号量通信,后期我们在讨论。

 

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++ 中,我们可以使用线程库来实现多线程编程。线程的挂起、唤醒与终止是多线程编程中非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ 中,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ 中,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量的线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量的线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待子线程执行完成,否则会导致程序崩溃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不忘初心t

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值