zoukankan      html  css  js  c++  java
  • 第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 具有消息传递的同步操作

  • 相关阅读:
    PAT 1097. Deduplication on a Linked List (链表)
    PAT 1096. Consecutive Factors
    PAT 1095. Cars on Campus
    PAT 1094. The Largest Generation (层级遍历)
    PAT 1093. Count PAT's
    PAT 1092. To Buy or Not to Buy
    PAT 1091. Acute Stroke (bfs)
    CSS:word-wrap/overflow/transition
    node-webkit中的requirejs报错问题:path must be a string error in Require.js
    script加载之defer和async
  • 原文地址:https://www.cnblogs.com/hebust-fengyu/p/12087822.html
Copyright © 2011-2022 走看看