(一)通用工具之同步队列(sync_queue)

介绍

我们经常需要在多线程间通信,例如网络通信线程和逻辑线程,网络线程需要把收到的数据 传递到 逻辑线程进行处理;同样 逻辑线程 需要把发送的数据,传递到网络线程进行发送。 这时我们就需要一种数据结构 同步队列。
由于C++11 对线程提供了支持,我们需要一种支持 先入先出的数据结构即可 ,STL库里面已经有现成的 std::deuqe, std::queue。但C++11 引入了右值引用,类的成员函数添加了移动构造函数,利用这个特性让 std::vector在某些操作情况下可能性能更佳。

同步队列的操作和普通的一样:队尾插入,队头出队。

入队

为了和stl里面的容器操作接口保持一致,入队函数如下

		void push_back(const T& x)
		{
			std::unique_lock<std::mutex> lck(_mutex);
			_queue.push_back(x);
		}


这个函数很简单,就是对容器加锁,然后插入数据,防止多个线程同时对队列操作。

C++11 中许多STL容器的插入操作 引入了一个新的函数 emplace_back/emplace, 这个同样是和右值引用相关,如果插入的数据是右值,那就会调用这个数据的移动构造函数,而不是拷贝构造函数,这样就会比 push_back() 少一次拷贝。同样我们也实现这个操作:
		template<typename _Tdata>
		void emplace_back(_Tdata&& v)
		{
			std::unique_lock<std::mutex> lck(_mutex);
			_queue.emplace_back(std::forward<_Tdata>(v));
		}

和push_back 一样,先加锁,再操作容器。_Tdata 是一个未定的引用类型,可以是右值或者左值,由具体传入的参数确定(详见C++11相关资料)。由于这个模板函数被调用后就已经实例化,Tdata 将具有确定的类型,在函数内部将会变为左值,std::forward 被称为完美转发,将会保持参数的原有类型,传递给另一个函数。这样我们就可以把右值引用类型参数传递给 容器的 emplace_back 函数。

出队

出队函数入下
		T pop_front()
		{
			std::unique_lock<std::mutex> lck(_mutex);
			if(_queue.empty())
			{
				return T();
			}
			assert(!_queue.empty());
			T t(_queue.front());
			_queue.pop_front();
			return t;
		}

先加锁,如果队列为空,则返回一个默认的对象。不为空则弹出队首数据。下面将会提供一个获取队列长度的函数。 使用的时候应该 先检查长度 再出队操作。

获取队列长度

		size_t size()
		{
			std::unique_lock<std::mutex> lck(_mutex);
			return _queue.size();
		}


返回容器数据个数即可。

Move操作

一般情况下 我们是 边入队,边出队,由于每个操作都是对队列中的一个元素的操作,可能更加频繁的加锁解锁。使用std::move 返回容器的右值引用对象,这样可以获取容器中的所有元素,并且清空容器,这是一个批量操作,比单个元素操作更高效。

		std::deque<T> move()
		{
			std::unique_lock<std::mutex> lck(_mutex);
			auto tmp = std::move(_queue);
			m_notFull.notify_one();
			return std::move(tmp);
		}

实现代码

#pragma once
#include <mutex>
#include <condition_variable>
#include <cassert>
#include <type_traits>
#include <atomic>

namespace moon
{
	template<typename T, typename TContainer = std::deque<T>  , size_t max_size = 50>
	class sync_queue
	{
	public:
		sync_queue()
			:m_exit(false)
		{
		}

		sync_queue(const sync_queue& t) = delete;
		sync_queue& operator=(const sync_queue& t) = delete;

		void push_back(const T& x)
		{
			std::unique_lock<std::mutex> lck(m_mutex);
			m_notFull.wait(lck, [this] {return m_exit || (m_queue.size() < max_size); });
			m_queue.push_back(x);
		}

		template<typename _Tdata>
		void emplace_back(_Tdata&& v)
		{
			std::unique_lock<std::mutex> lck(m_mutex);
			m_notFull.wait(lck, [this] {return m_exit || (m_queue.size() < max_size); });
			m_queue.emplace_back(std::forward<_Tdata>(v));
		}

		size_t size()
		{
			std::unique_lock<std::mutex> lck(m_mutex);
			return m_queue.size();
		}

		//替代pop_front
		TContainer move()
		{
			std::unique_lock<std::mutex> lck(m_mutex);
			auto tmp = std::move(m_queue);
			m_notFull.notify_one();
			return std::move(tmp);
		}

		//当程序退出时调用此函数,触发条件变量
		void exit()
		{
			m_exit = true;
		}

	private:
		std::mutex							m_mutex;
		std::condition_variable		m_notFull;
		TContainer							m_queue;
		std::atomic_bool					m_exit;
	};

}


TContainer支持 std::vector,std::deque. 这里取消了pop_front 操作,因为 std::vector 没有pop_front, 为了统一 使用 move函数。可以给队列限制大小,防止一直入队,占用太多内存。

示例

这个示例演示了使用同步队列进行 异步 加法计算

#include <thread>
#include "sync_queue.h"

struct SAddContext
{
	SAddContext()
		:a(0),b(b)
	{

	}
	int a;
	int b;
};

struct SAddResult
{
	SAddResult()
		:a(0), b(0),result(0)
	{

	}
	int a;
	int b;
	int result;
};

int main()
{
	moon::sync_queue<SAddContext> que1;//main thread - calculate thread
	moon::sync_queue<SAddResult> que2;//calculate thread - print thread

	std::thread calculate([&que1,&que2]() {
		while (1)
		{
			//如果队列为空 ,等待
			if (que1.size() == 0)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
			}

			//获取所有异步计算请求
			auto data = que1.move();
			for (auto& dat : data)
			{
				SAddResult sr;
				sr.a = dat.a;
				sr.b = dat.b;
				sr.result = dat.a + dat.b;
				que2.push_back(sr);
			}
		}
	});

	std::thread printThread([&que2]() {
		while (1)
		{
			//如果队列为空 ,等待
			if (que2.size() == 0)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
			}

			auto data = que2.move();
			for (auto& dat : data)
			{
				printf("%d + %d = %d\r\n", dat.a, dat.b, dat.result);
			}
		}
	});

	int x = 0;
	int y = 0;

	while (std::cin >> x >> y)
	{
		SAddContext sc;
		sc.a = x;
		sc.b = y;
		que1.push_back(sc);
	}
};




`blk_sync_queue` 是 Linux 内核中一个与磁盘块设备操作相关的概念,它用于在 I/O 请求处理过程中提供同步机制。以下是对其各部分的详细介绍: ### 1. `blk` `blk` 指的是 Linux 内核中的块设备层(Block Device Layer),它是操作系统管理磁盘、闪存等块设备的基础架构。块设备层负责抽象底层物理存储设备,如硬盘或 SSD,并向上层应用提供统一的接口来读写数据。 ### 2. 同步队列 (`sync queue`) 在块设备操作中,特别是涉及到磁盘 I/O 的情况下,数据可能会被分散到多个系统调用或硬件操作之间传输,这可能导致数据一致性问题。例如,在一个连续的大文件写入操作中,如果由于某种原因(如内存不足)导致数据分批写入到缓存中而不是一次性提交到磁盘,那么可能存在数据丢失的风险。同步队列正是为了应对这种情况而设计的。 ### 3. `blk_sync_queue` 的功能 - **确保数据完整性**:通过强制将所有已提交但未最终写入磁盘的数据缓冲区同步到磁盘,`blk_sync_queue` 确保了数据的一致性和完整性。 - **等待数据完全写入**:在执行 `sync` 操作后,直到磁盘上的实际写入完成,内核才会继续执行后续任务。这意味着在进行下一次 I/O 操作前,当前的所有数据都已被安全地保存到了磁盘上。 - **避免数据损坏风险**:在多线程环境中,尤其是在并发访问磁盘时,同步队列有助于减少因并发操作而导致的数据不一致或错误情况的发生。 ### 4. 使用场景 `blk_sync_queue` 主要应用于需要保证数据完整性的关键业务场景,比如日志记录、数据库事务处理、备份操作和任何对数据持久性有严格要求的应用程序。在这些场景中,即使面临系统故障或电源中断等情况,也能确保数据不会丢失。 总之,`blk_sync_queue` 在提升系统稳定性和可靠性方面发挥了重要作用,是 Linux 内核管理和保护块设备数据完整性的重要工具之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值