第4章 同步并发操作

时间:2019-12-27
本文章向大家介绍第4章 同步并发操作,主要包括第4章 同步并发操作使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

4.1 等待事件或其他条件

我们在配合做事情的时候,总是应该在前提条件完成的情况下,才会开始进行下一步操作,所以,我们可以选择以下几种方式:

1.实时查看前提条件的完成情况。这样可以及时的进行下一步任务,但是,这样的操作是一件十分浪费时间的事情,在这期间本来还可以做其他事情,现在由于实时关注前提条件的完成情况,不得不一直专注于这件事情。

2.每隔一定时间查看前提条件的完成情况。这样虽然我们可以在这些间断的时间用来做一些其他的事情,但是,这种查询方式不利于前提条件完成后及时进行下一步的任务。

3.让完成前条件的人,在完成该任务后及时通知你。这种方法可以避免中间的时间浪费,也可以及时的进行下一步的任务,是一件十分高效的策略。

故C++标注库中的条件变量等待条件正是第三种情况。

4.1.1 用条件变量等待条件

C++标准库中提供了两个条件变量的实现:std::condition_variable和std::condition_variable_any。前者std::condition_variable只能与std::mutex一起配合使用,但是后者std::condition_variable_any则可以与复核成为类似互斥元的最低标准的任何东西一起工作,但同时在大小、性能和操作系统资源方面有着额外的代价。下面展示std::condition_variable的使用。

#include<iostream>
#include<condition_variable>
#include<mutex>
#include<chrono>
#include<thread>

using namespace std;

mutex m;
condition_variable cv;
bool flg;


void pre_print(){
	
	cout<<"pre_print"<<endl;
	flg = true;
	cv.notify_one();
}

void after_print(){
	unique_lock<mutex> lg(m);
	cv.wait(lg, []{return flg;});
	cout<<"after_print"<<endl;
}


int main(){
	flg = false;
	thread t1(after_print), t2(pre_print);
	t1.detach();
	
	//std::this_thread::sleep_for(std::chrono::seconds(3));

	t2.join();
	cout<<"finish_process"<<endl;
	return 0;
}

4.1.2 使用条件变量建立一个线程安全队列

为queue书写一个安全的支持多线程并发的数据结构--safe_queue。

相关代码有时间再补

4.2 使用future等待一次事件

比如调用函数得到返回时,有时候这种事情只发生一次,在单线程中很容易解决,在多线程中我们要借助C++标准库中的future来解决,虽然future支持多线程通讯,但这种工具不支持同步访问,如果多线程需要同步访问同一个future应该使用互斥元等来解决。

在C++中关于future有两种:

1.future :future实例时仅有的一个指向其关联事件的实例。

2.shared_future :多个shared_future的实例可以指向同一个事件。

4.2.1 从后台任务中返回值

在使用多线程时,我们会想到thread,但是这种机制没有提供接收返回值的特性,所以我们现在要学习另一个用于多线程的函数模板,std::async,这个函数的使用方式和thread相似,但是结果返回一个std::future的对象,而不是一个thread对象让你等待,这个future对象最终持有该多线程函数的返回值,最终只需要在future对象上使用get函数即可得到函数返回值。例如

#include<thread>
#include<future>
#include<iostream>
#include<vector>
using namespace std;

vector<int> add(){
	vector<int> ans;
	for(int i = 0; i < 10; ++i)
		ans.push_back(i);
	return ans;
}


int main(){
	future<vector<int>> res = async(add);
	vector<int> ans = res.get();
	for(auto i: ans)
		cout<<i<<' ';
	cout<<endl;
	return 0;
}

虽然上面的例子用单线程也能解决,但是上面的例子只是为说明该函数的使用方法。async也可以指定多线程调用的方法,async(std::launch::deferred表明在调用中一直延迟到使用future.wait()或者future.get()为止,但是async(std::launch::async)则表明必须在他自己的线程上运行。(注:根据课本中的几个字在wait()或者get()中运行,我感觉应该是在this_thread中运行,只是在wait或者get函数中再次启动该线程,如有不对希望高手指正)。

4.2.2 将任务与future相关联

C++标准库中有个类std::package_task<>可以将future与绑定到一个函数或者可调用对象上,当std::package_task<>被调用时,绑定的函数或者可调用对象也被调用,并且使future进入就绪状态,将返回值作为关联数据存储。当std::package_task<>作为可调用对象时被调用时,提供给函数调用运算符的参数被传给所包含的函数,并且将结果作为异步结果返回,存储在由get_future()获得的std::future中。使用书中示例如下

#include<deque>
#include<mutex>
#include<future>
#include<thread>
#include<utility>

using namespace std;

mutex m;
deque<package_task<void()>> tasks;

bool gui_shutdown_message_received();
void get_and_process_gui_message();

void gui_thread(){
	while(!gui_shutdown_message_received()){
		std::package_task<void()> task;
		{
			lock_guard<mutex> lg(m);
			if(tasks.empty())
				continue;
			task = std::move(task.front());
			task.pop_front();
		}
		task();
	}		
}	

std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f){
		package_task<void()> task(f);
		future<void> res = task.get_future();
		lock_guard<mutex> lg(m);
		tasks.push_back(std::move(task));
		return res;
} 

4.2.3 生成std::promise

在我看来std::promise是一种用于线程间通讯的工具,std::promise<T>提供一种设置值(类型T)的方式,他可以在这之后通过相关联的std::future<T>对象进行读取。一对std::promise/std::future为这一设施提供了一种可能的机制,等待中线程可以阻塞future,同时提供数据的线程可以使用配对中的promise向,来设置相关的值并使future就绪。

同时你可以使用get_future()成员函数来获取与给定的std::promise相关的std::future对象。当设置完promise的值(使用set_value()成员函数),,future就会变为就绪,,并且可以用来获取锁存储的值。如果销毁std::promise时未设置值,则将存入一个异常。示例如下

#include<iostream>
#include<future>
#include<condition_variable>
#include<chrono>
#include<thread>
#include<mutex>

using namespace std;

promise<int> ps;
mutex m;
condition_variable cv;
bool flg;

void get_and_show_val(){
	int t;
	
	unique_lock<mutex> ul(m);
	cv.wait(ul, [](){return flg;});
	flg = false;
	future<int> ans = ps.get_future();
	t = ans.get();
	cout<<t<<endl;

}

void set_val(){


	unique_lock<mutex> ul(m);
	ps.set_value(3);
	flg = true;
	cv.notify_one();
	ul.unlock();
	this_thread::sleep_for(chrono::seconds(3));
		

}

int main(){
	flg = false;
	thread t1(get_and_show_val), t2(set_val);
	t1.detach();
	t2.join();
	return 0;
}		 

根据https://zh.cppreference.com/w/cpp/thread/promise 说明该promise只应该使用一次。

4.2.4 为future保存异常

当然也可以使用promise,但是在设置值的时候使用set_exception(),而不是set_value()。

std::promise<double> some_promise;
try{
	some_promise.set_val(calc_val());
}
catch{
	some_promise.set_exception(std::current_exception());
}

4.2.5 等待自多个线程

当多个线程访问std::furture对象而不进行额外同步时,就会出现数据竞争和未定义的行为,这是有意为之的,future模型统一异步结果的所有权,同时get()的单发性质是的这样的并发访问没有意义——只有一个线程可以获取值,因为在首次调用get()之后,就没有任何可获得的值留下了,但是这时我们可以使用std::shared_future使std::future交出所有权达到可以复制的效果,有几种方式可以使用shared_future

future<int> f;
share_future<int> sf(f);
//或者
// share_future<int> sf = f.share();

4.3 有时间的等待

关于超时有两种:

1.基于时间段:等待一个指定的时间长度。函数形式为_for

2.基于时间点(绝对超时):等到指定的时间点。函数形式为_until

4.3.1 时钟

时钟是时间信息的来源,具体来说,时钟提供以下四个不同部分信息的类。

1.现在时间

2.用来表示从时钟获取到的时间值的类型

3.时钟的节拍周期

4.时钟是否以均匀的速率进行计时,决定其是否为匀速(steady)时钟

对于具体的某个时间点类型是time_point成员的typedef

4.3.2 时间段

时间段是时间支持中的最简单部分,他们是由std::chrono::duration<>来进行处理的,第一个模板代表类型,第二个模板是分数,代表每个时间段表示多少秒,short存储的几分钟表示为std::chrono::duration<short, std::radio<60,1>>,以double存储的毫秒数则表示为std::chrono::duration<double, std::radio<1,1000>>表示1毫秒为1/1000秒

在无需截断的场合下,时间段之间的转换时隐式的(因此将小时转换为秒是可以的,但将秒转换为小时则不然),显式转换可以通过std::chrono::duration_cast<>来转换。

4.3.3 时间点

时间点是通过std::chrono::time_point<>类模板来管理的。

4.3.4 接收超时的函数

类/命名空间 函数 返回值
std::this_thread命名空间

 sleep_for(duration)

sleep_until(time_point)

不可用

std::condition_variable或者

std::condition_variable_any

wait_for(lock,duration)

wait_until(lock, time_point) 

std::cv_status::timeout或

std::cv_status::no_timeout

 

wait_for(lock, duration, predication)

wait_unitl(lock, time_point, predition) 

bool——当唤醒时predicate的返回值

std::time_mutex或者

std::recursize_time_mutex

try_lock_for(duration)

try_lock_until(time_point) 

bool——true如果获得了锁,否则false
std::unique_lock<TimedLockable>

unique_lock(lockable, duration)

unique_lokc(lockable, time_point) 

不可用——owns_lock()在新构造的对象上,如或获得了锁返回true,否则false
 

try_lock_for(duration)

try_lock_until(time_point) 

bool——如果获得了锁,否则返回false

std::future<ValueType>或者

std::shared_future<ValueType>

wait_for(duration)

wait_until(time_point) 

std::future_status::timeout如果等待超时,

std::future_status::ready如果future就绪或

std::future_status::deferred如果future持有的延迟函数还没有开始

4.4 使用操作同步简化代码

4.4.1 带有future的函数式编程

4.4.2 具有消息传递的同步操作

原文地址:https://www.cnblogs.com/hebust-fengyu/p/12087822.html