2024年基于c++11的信号量(携带数据)_c+,C C++框架

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

在多线程工作中,线程同步是绕不过的一个问题。线程同步的方式主要有互斥量、临界区、原子操作等,本文主要研究信号量在线程同步中的作用。

信号量的实质是一把数据锁,本身并不具备数据传递的功能。但我在实际使用中,常常会需要在发送一个通知的同时,传递数据给线程,因此就希望能让信号量拥有数据传递的能力。

1. condition_variable和mutex

c++11中,本身便提供了很方便的信号量实现方案,即利用条件变量condition_variable和互斥量mutex,来实现信号量。

我们一步步来,首先来一个最简单的:

#include <condition_variable>

class cv_reusable_semaphore
{
public:

	cv_reusable_semaphore()
	{}

	~cv_reusable_semaphore()
	{}

	void wait()
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		m_cv.wait(lock);//*注释1
	}

	void notify_all()
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		m_cv.notify_all();
	}

	void notify_one()
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		m_cv.notify_one();
	}

private:

	std::condition_variable m_cv;

#ifdef USE_RECURSIVE_MUTEX
	std::recursive_mutex m_recursive_mutex;
#else
	std::mutex m_mutex;
#endif

};

互斥量可以选择mutex或recursive_mutex,但条件变量condition_variable只能选用unique_lock锁。

以上的代码确实可以使用,但存在一个问题,也就是在[注释1]处的wait()阻塞。可能在没有收到notify的情况下,也会被唤醒,这就是条件变量的虚假唤醒(spurious wakeups)问题。解决这个问题最常见的方式,就是在wait()外面加上while循环进行判断,避免虚假唤醒的情况。

接下来我们解决这个虚假唤醒问题,并加上数据传递的功能:

#include <condition_variable>

template<class _type>
class cv_reusable_semaphore
{

	//标识该信号量当前所处的状态
	enum class flag
	{
		wait = 0,	//等待状态
		notify_all,	//已发送notify_all
		notify_one,	//已发送notify_one
		received,	//已接收
	};

public:

	cv_reusable_semaphore()
		:m_flag(flag::wait)
	{}

	~cv_reusable_semaphore()
	{}

	_type wait()
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		//避免虚假唤醒
		while (m_flag == flag::wait)
		{
			m_cv.wait(lock);
		}
		//若发送的状态为notify_all,则置为已接收状态
		if (m_flag == flag::notify_all)
		{
			m_flag = flag::received;
		}
		//若发送的状态为notify_one,则置为等待状态,等待接收下一个通知
		if (m_flag == flag::notify_one)
		{
			m_flag = flag::wait;
		}
		return m_data;
	}

	void notify_all(_type d)
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		m_data = d;
		m_flag = flag::notify_all;
		m_cv.notify_all();
	}

	void notify_one(_type d)
	{
#ifdef USE_RECURSIVE_MUTEX
		std::unique_lock<std::recursive_mutex> lock(m_recursive_mutex);
#else
		std::unique_lock<std::mutex> lock(m_mutex);
#endif
		m_data = d;
		m_flag = flag::notify_one;
		m_cv.notify_one();
	}

private:

	std::condition_variable m_cv;

#ifdef USE_RECURSIVE_MUTEX
	std::recursive_mutex m_recursive_mutex;
#else
	std::mutex m_mutex;
#endif

	flag m_flag;

	_type m_data;

};

和之前的代码相比,为了可以传递各种类型的参数,这个类修改成了模板类,并且添加了两个参数,分别是m_flag和m_data。

m_flag是一个枚举对象,用以标识信号量当前的状态。

m_data是模板类型的对象,用以保存和传递数据。

根据以上代码,不难看出 template<class _type> class cv_reusable_semaphore 拥有以下特性:

1.每一个对象可以建立多个发送和多个接收;

2.每一个对象都可以重复使用,但如果前一个发送没有被接收,再次发送的数据会覆盖前一个发送的数据;

3.wait()和notify_*()函数调用先后顺序不影响结果。若wait()先被调用,则进入阻塞,调用的线程阻塞,等待notify_*()被调用。若notify_*()先被调用,则会将消息存储,当wait()被调用时,不会进入等待,直接将存储的值取;

2. promise和future

2.1. class semaphore

除了用condition_variable和m_mutex来实现信号量之外,c++11里还有一对好基友,也就是promise和future。

利用这一对好基友,也可以很简单的实现我的目的,也就是可以传递数据的信号量,同样也是从最简单的开始:

#include <future>

#define SEMAPHORE_STATE_NULL 0 //初始状态
#define SEMAPHORE_STATE_PROMISE 1 //已发送
#define SEMAPHORE_STATE_FUTURE 2 //已接收

typedef unsigned int semaphore_state;

template<typename _type>
class semaphore
{

public:

	semaphore()
		:m_state(SEMAPHORE_STATE_NULL)
	{}

	~semaphore()
	{}

	_type wait()
	{
		m_state |= SEMAPHORE_STATE_FUTURE;
		std::future<_type> f = m_promise.get_future();
		return f.get();
	}

	void notify(_type data)
	{
		m_state |= SEMAPHORE_STATE_PROMISE;
		m_promise.set_value(data);
	}

	semaphore_state state()
	{
		return m_state;
	}

private:

	std::promise<_type> m_promise;

	semaphore_state m_state;

};

可以看到用promise和future来实现的话,代码更为简洁(甚至其中和semaphore_state有关的变量和宏定义,对于这一个类的功能而言没有意义,是为了配合之后实现信号量队列queue_semaphore而添加的)。

基于promise和future的特性,semaphore类拥有不同于cv_reusable_semaphore类的特性:

1.每个对象只能有一个发送和一个接收;

2.每一个对象允许传递一个信号,信号传递完毕后,该对象不可用;

3.wait()和notify()函数调用先后顺序不影响结果(这一点和cv_reusable_semaphore 相同,且以下所有信号量都有该特性,因此在后文中,该特性省略不写);

简单概括一下以上特性:也就是说semaphore类的对象是一次性的,只能进行一次发送(notify)和一次接收(wait),在完成一次发送和接收之后,对象便失去了作用,不允许再次发送或接受。

可以看到,后文中的信号量队列便是利用了semaphore类这种一次性使用的特点。

2.2. class shared_semaphore 和 reusable_semaphore

2.2.1. class shared_semaphore

如果对semaphore类进行一些细微的修改,就能得到不同特性的信号量,例如下面代码,是将future改为了shared_future:

template<typename _type>
class shared_semaphore
{

public:

	shared_semaphore()
		:m_state(SEMAPHORE_STATE_NULL)
	{
		m_future = m_promise.get_future().share();
	}

	~shared_semaphore()
	{}

	_type wait()
	{
		return m_future.get();
	}

	void notify(_type data)
	{
		m_state |= SEMAPHORE_STATE_PROMISE;
		m_promise.set_value(data);
	}

	semaphore_state state()
	{
		return m_state;
	}

private:

	std::promise<_type> m_promise;

	std::shared_future<_type> m_future;

	semaphore_state m_state;

};

shared_semaphore类拥有以下特性:

1.每一个对象允许传递一个信号,信号发送后,不允许再次发送,但可以继续接收;

2.每个对象只能有一个发送但可以有多个接收;

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

1.每一个对象允许传递一个信号,信号发送后,不允许再次发送,但可以继续接收;

2.每个对象只能有一个发送但可以有多个接收;

[外链图片转存中…(img-5Iq78M2I-1715594606811)]
[外链图片转存中…(img-swJjBMju-1715594606811)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值