c++线程间共享数据的问题
链接: https://pan.baidu.com/s/1d6YO... 提取码: iamh
作者-\/ 307570512
所有线程间共享数据的问题,都是批改数据导致的(竞争条件)。如果所有的共享数据都是只读的,就没问题,因为一个线程所读取的数据不受另一个线程是否正在读取雷同的数据而影响。
防止有问题的竞争条件 1.用爱护机制封装你的数据结构,以确保只有理论执行批改的线程可能在不变量损坏的中央看到两头数据。 2.批改数据结构的设计及其不变量,从而令批改作为一系列不可分割的变更来实现,每个批改均保留其不变量。者通常被称为无锁编程,且难以尽如人意。
用互斥元爱护数据 在清单3.1 用互斥元爱护列表中,有一个全局变量,它被相应的std::mutex的全局实例爱护。在add_to_list()以及list_contains()中对std::lock_guardstd::mutex的应用意味着这些函数中的拜访是互斥的list_contains()将无奈再add_to_list()进行批改的半途看到该表。
留神:一个迷路的指针或援用,所有的爱护都将徒劳。在清单3.2 意外地传出对受爱护数据的援用展现了这一个谬误的做法。
发现接口中固有的竞争条件,这是一个粒度锁定的问题,就是说锁定从语句回升到接口了,书中用一个stack类做了一个扩大,详见清单3.5 一个线程平安栈的具体类定义
死锁:问题和解决方案:为了防止死锁,常见的倡议是始终应用雷同的程序锁定者两个互斥元。 std::lock函数能够同时锁定两个或更多的互斥元,而没有死锁的危险。 常见的思路:
防止嵌套锁
在持有锁时,防止调用用户提供的代码
以固定程序获取锁 这里有几个简略的事例:清单3.7 应用锁档次来防止死锁、清单3.9 用std::unique_lock灵便锁定
锁定在失当的粒度 特地的,在持有锁时,不要做任何耗时的流动,比方文件的I/O。 个别状况下,只应该以执行要求的操作所需的最小可能工夫而去持有锁。这也意味着耗时的操作,比方获取获取另一个锁(即使你晓得它不会死锁)或是期待I/O实现,都不应该在持有锁的时候去做,除非相对必要。 在清单3.10 在比拟运算符中每次锁定一个互斥元尽管缩小了持有锁的工夫,然而也裸露在竞争条件中去了。
用于爱护共享数据的代替工具 二次检测锁定模式,留神这个和单例模式中的饱汉模式不一样,它前面有对数据的应用
void undefined_behaviour_with_double_checked_locking()
{
if(!resource_ptr){ std::lock_guard<std::mutex> lk(resource_mutex); if(!resource_ptr) { resoutce_ptr.reset(new some_resource); }}resource_ptr->do_something();
}
它有可能产生顽劣的竞争条件,因为在锁内部的读取与锁外部由另一线程实现的写入不同步。这就因而创立了一个竞争条件,不仅涵盖了指针自身,还涵盖了指向的对象。
C++规范库提供了std::once_flag和std::call_once来解决这种状况。应用std::call_once比显示应用互斥元通常会由更低的开销,特地是初始化曾经实现的时候,应优先应用。清单3.12 应用std::call_once的线程平安的类成员提早初始化
爱护很少更新的数据结构:例如DNS缓存,应用读写互斥元:单个“写”线程独占拜访或共享,由多个“读”线程并发拜访。 清单3.13 应用boost::share_mutex爱护数据结构
main 函数的返回值为 int 类型,留神不要写成 void 类型。
C/C++气象数据中心实战
C/C++手把手教你做工业级我的项目
//std::accumulate的并行版本(来自清单2.8)template <typename Iterator,typename T>struct accumulate_block{ void operator()(Iterator first,Iterator last,T& result) { result=std::accumulate(first,last,result); }};template <typename Iterator,typename T>T parallel_accumulate(Iterator first,Iterator last,T init){ unsigned long const length=std::distance(first,last); if(!length) //如果输出的范畴为空,只返回初始值init return init; unsigned long const min_per_thread=25; //最小块的大小 unsigned long const max_threads=(length+min_per_thread-1)/min_per_thread; //解决的元素数量除以最小块的大小,获取线程的最大数量 unsigned long const hardware_threads=std::thread::hardware_concurrency(); //真正并发运行的线程数量的批示 //要运行的线程数是你计算出的最大值的硬件线程数量的较小值。 unsigned long const num_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads); //如果hardware_concurrency返回0,咱们就替换成2,运行过多的线程,会在单核机器上变慢,过少会错过可用的并发 unsigned long const block_size=length/num_threads; //待处理的线程的条目数量是范畴的长度除以线程的数量 std::vector<T> results(num_threads); //保留两头后果 std::vector<std::thread> threads(num_threads-1); //因为有一个线程(本线程)了所以少创立一个文档 //循环:1.递进block_end到以后块的结尾,2.并启动一个新的线程来累计此块的后果。3.下一个块的开始是这一个的完结 Iterator block_start=first; for(unsigned long i = 0; i < (num_threads-1);++i) { Iterator block_end=block_start; std::advance(block_end,block_size); ...1 threads[i]=std::thread(accumulate_block<Iterator,T>(),block_start,block_end,std::ref(results[i])); ...2 block_start=block_end; ...3 } //这里是解决下面没有整除的掉block_size的剩下的局部 accumulate_block()(block_start,last,results[num_threads-1]); //通过join期待所有计算的线程 std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); //一旦累计计算出最初一个块的后果,调用accumulate将后果计算出来 return std::accumulate(results.begin(),results.end(),init);}