IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11实现一个cyclic barrier -> 正文阅读

[C++知识库]C++11实现一个cyclic barrier

上文中使用计数器作为同步事件实现了latch,其实在多线程并发编程实践中,还有一种使用计数器作为同步事件的机制:Cyclic-Barrier,即循环屏障的意思。它指定了参与执行线程的数量,当某个线程运行到指定位置时就处于等待状态,只有当这些线程全部都到达该位置时,它们才能一起从等待状态中退出。它的实现原理也是以一个计数器作为同步变量,当某个线程到达指定的位置时,就让计数器的值减少1,如果结果不为0时,线程就进入等待状态,只有当计数器减到0时,即指定数量的线程全部到达了指定位置,此时所有在该位置等待的线程全部被唤醒,然后执行各自后续的流程。

下面是cyclic_barrier类的定义:

class cyclic_barrier {
	mutable std::mutex m;
	std::condition_variable cv;
	const int parties;
	volatile int count;
	volatile bool broken;
	std::function<void(void)> callback;

public:
	cyclic_barrier(int n);
	cyclic_barrier(int n, std::function<void(void)> f);
	~cyclic_barrier() {}
	
	cyclic_barrier(const cyclic_barrier&) = delete; 
	cyclic_barrier& operator=(const cyclic_barrier&) = delete;
	
	void wait();
	int get_waitings() const;
	int get_parties() const;
	void cancel();
};

既然线程有需要等待唤醒、通知的机制,使用互斥量m和条件变量cv就是不二之选了,使用它们进行线程的等待和唤醒操作。
为了进行计数,还需要一个整型的数据成员count来存放计数器,每当某个线程进入同步点时,就让count减1,直到为0;因为要循环计数,还需要保存计数器的初始值parties,它是参与线程的个数,初始化后就不会修改了,使用const修饰。
回调函数callback用于当最后一个线程到达后唤醒其它线程前,所执行的操作。
此外还有用于标记中断的broken变量,线程被唤醒后会检查该值,判断是否是被提前中断了。

看一下各个成员函数的实现。
1、构造函数

cyclic_barrier::cyclic_barrier(int n) : parties(n), callback([](){}) {
	if (n <= 0) {
		throw std::string("invalid parameter!");
	}
	
	count = n;
	broken = false;
}

cyclic_barrier::cyclic_barrier (int n, std::function<void(void)> f) : cyclic_barrier(n) {
	callback = move(f);
}

构造函数有两个,如果不需要回调函数的话,可以使用第一个构造函数来创建对象,它指定了一个缺省实现的回调函数。 如果有自定义的回调函数,可以使用第二个构造函数来创建对象。参数n来指定参与者线程的数量,该参数必须大于0,否则直接抛出异常,构造失败。

2、wait函数

void cyclic_barrier::wait() {
	std::unique_lock<std::mutex> ul(m);
	if (--count == 0) {
		callback(); // callback是在临界区内执行
		cv.notify_all();
		count != parties; // 复位计数器
		return; // 如果是最后一个到达的线程,显然条件满足了,就直接返回
	}

	while (count != 0 && count != parties && !broken) {
		cv.wait(ul);
	}

	if (broken) {
		throw broken_exception();
	} 
}

每当有一个线程调用了wait(),就让count减1,并检查count是否为0, 如果不为0,说明还有别的线程没有到达,就进入等待状态;如果为0,说明这些线程全部到达,就先调用回调函数,然后唤醒其它所有等待中的线程。因为是循环barrier,在count为0时,重新设置为parties,等待下一轮的线程。

回调函数执行完之后再唤醒其它线程,这样,如果在回调函数中修改了共享变量,可以保证在其它线程唤醒之前修改完成,以保证共享变量的访问安全。

wait()函数不妨看作是一个屏障(barrier),线程到达此位置时,都不能越过它,只能处于等待状态,直到最后一个线程到达。既然是屏障,那么线程在wait()之前所做的操作,如果涉及到共享变量的话,它们对共享变量所作的修改,也就都happen-before这个屏障之前,从而当所有线程从wait()中唤醒后,进行后续操作时,如果访问这些共享变量,都是发生在这个屏障之后,从而保证了这些共享变量的整体happen-before语义。同样,当最后一个线程到达,调用回调函数也不会越过wait(),如果在回调函数中也有修改共享变量的操作,这个操作同样也happen-before于线程唤醒后的后续操作。

线程在等待过程中也可以被打断,也就是说,即使还没有规定数量的线程调用wait(),也可以强制中断它。程序中使用broken作为中断标记,中断后它被设置为true,线程被唤醒后,如果发现broken=true,则说明是被中断唤醒的,此时线程就抛出broken异常,通知调用者。

4、中断函数

void cyclic_barrier::cancel() {
	std::lock_guard<std::mutex> lg(m);
	broken = true;
	cv.notify_all();
}

如果想要提前中断barrier,通过设置中断标记broken为true,并唤醒全部处于等待中的线程即可,此时线程从wait()中被唤醒后会抛出broken_exception异常。

class broken_exception {
};

3、其它函数

int cyclic_barrier::get_waitings() const {
	std::lock_guard<std::mutex> lg(m);
	return parties - count;
}

int cyclic_barrier::get_parties() const {
	return parties;
}

get_waitings()用来查询处于等待中的线程个数,get_parties()用来查询参与者的线程数量,它们都是const成员函数。

与latch相比,它们有如下特点:
1、cyclic_barrier的计数器统计的是到达屏障点的线程数量,即计数器的初始值是参与者线程的个数;而latch的计数器是根据所要满足的条件而设置的的数量,和线程数量不一定相关。

2、cyclic_barrier的计数器减少到0后,它会被复位,重新设置为初始化时的值,可以被循环使用;而latch是一个一次性的事件,当计数器变为0之后,就不再使用了。

3、使用cyclic_barrier的线程进入等待和唤醒都是同一个函数:wait(),线程在执行过程中调用它时,相当于在该函数的调用位置处设置了一个屏障点,先到达的线程会在屏障点等待后到达的,只有在所有线程全部到达这个屏障点之后,它们才能同时越过这个屏障点;而latch一般是由两种不同性质的线程相互协作,使用了等待和通知机制,一种统一在某个位置等待(调用wait()),另一种通过检查计数器为0时来通知唤醒(调用countdown())。

4、cyclic_barrier有回调函数,在唤醒所有线程之前,可以执行这个回调函数。

示例:
多个线程从同一个位置同时运行。

void test(cyclic_barrier & barrier) {
	std::cout << "ready:" << std::this_thread::get_id() << std::endl;
	try {
		// .. 线程运行的准备工作,准备完毕之后,等待同时启动! 
		barrier.wait(); // 等待同时启动 
	} catch (broken_exception &ex) {
		std::cout << "barrier broken!" << std::endl;
	}

	std::cout << "startup:" << std::this_thread::get_id() << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::cout << "finish:" << std::this_thread::get_id() << std::endl;
}

int main() {
	cyclic_barrier barrier(11, []{
		std::cout << "reach point" << std::endl;
	});

	std::vector<std::thread> vec;
	for (int i=0; i<10; i++) {
		vec.emplace_back(test, std::ref(barrier));
	}

	std::this_thread::sleep_for(std::chrono::seconds(2));
	std::cout << "waiting thread:" << barrier.get_waitings() << std::endl;
	barrier.wait(); // 主线程最后到达,通知所有线程一起运行 
	
	for (auto &t : vec) {
		t.join();
	}
}

barrier和latch都使用了计数器倒计数进行统计,它们的应用场景不太一样,为了便于区分它们,举两个生活中例子,可以体会一下它们的区别。
假如有5个好基友(即线程),商量好周末一块去风景区爬山,他们约定好周日上午在山脚下集合,不见不散。周日那天,如果第一个先到了(调用wait),发现没有其他人到达(count>0),就只好等着(cv.wait),第二个人到了之后,发现人还没有到齐,也只好等待,直到第5个人到达后,即所有5个人全部到齐了(count=0),做完准备工作之后(调用回调函数),就一起出发开始爬山(线程全部唤醒)。我们可以想象在山脚下立着一个栅栏(Barrier),如果它不放倒,人们无法翻越过去,而它被放倒的条件是,5个基友全部到达。如果这5个基友哪怕仅有一个还没有到达,它也不会被放倒,基友就被拦在外面,只有当5个基友全部到达之后,这个栅栏才会倒下,他们就可以进入山门了。这描述了应用barrier的场景。

如果换成另一种场景,这几个基友约定谁先来谁先爬,假设风景区大门的门闩(Latch)上共有2把锁(latch的数量),钥匙分别由门卫和值班经理保管,第一个基友来的比较早,此时大门还没有开,那么他就只能等这2个掌管钥匙的人员来开门:门卫来上班了,把他掌管的那把锁打开(倒计数减1),基友继续等待;当值班经理上班后打开了第二把锁之后(倒计数为0),这个基友进入大门开始爬山(从等待中被唤醒)。此后,其它基友陆续到达后,就无需等待了,因为所有的锁都已经打开了,他们可以直接进入景区爬山。这是应用latch的场景。

C++20中提供了类似的功能,详见barrier类。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-16 17:29:31  更:2021-12-16 17:31:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:32:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码