线程安全

在这三篇文章中,我将详细介绍原理操作,内存障碍和线程之间数据的快速交换,以及“sequence-points”示例中的“execute-around-idiom”。同时,我们将尝试一起做一些有用的事情。

标准C ++库中没有线程安全的容器(数组,列表,映射…),可以在多个线程中使用它们而无需附加锁。在使用标准容器进行多线程交换的情况下,可能会忘记使用互斥锁保护其中一个代码段,或者错误地使用另一个互斥锁来保护它。

显然,如果开发人员使用自己的解决方案而不是标准的解决方案,则会犯更多的错误。而且,如果任务很复杂,以至于没有任何标准解决方案,那么开发人员在尝试寻找解决方案时将充满错误。

依靠“实践大于理论”的原则,我们将努力创造出一个解决这个问题的最佳方法,而不是纸上谈兵。

在本文中,我们将实现使所有对象成为线程安全对象的智能指针,其性能与优化的无锁容器相同。

使用此类指针的简化,非优化示例:

int main() {
  contfree_safe_ptr< std::map< std::string, int > > safe_map_string_int;
  std::thread t1(& { safe_map_string_int->emplace(“apple”, 1); });
  std::thread t2(& { safe_map_string_int->emplace(“potato”, 2); });
  t1.join(); t2.join();
  std::cout << "apple = " << (*safe_map_string_int)[“apple”] <<
  ", potato = " << (*safe_map_string_int)[“potato”] << std::endl;
  return 0;
  }
  我们将引用标准的线程,以保证算法每一步的必要行为。

我们将详细考虑C ++内存模型以及线程之间的同步和数据交换的不同版本。

在本文中,我们将开发一个线程安全的指针safe_ptr<>。在第二篇文章中,我们将实现优化的“无争用共享互斥体”和contfree_safe_ptr<>基于其的优化指针。在第三篇文章中,我们将展示最佳利用示例contfree_safe_ptr<>并提供性能测试,并将其与高度优化的无锁容器进行比较。

背景

我们将从开发一个safe_ptr智能指针模板开始,该模板对于任何类型的T都是线程安全的,并且将在优化的无锁算法级别上显示多线程性能。

此外,这将使我们能够同时与几个不同的对象,同步工作,其中甚至对于无锁的数据结构,也将不得不使用锁,并且会出现永久死锁的风险。但是,我们将开发一个特殊的互斥锁类来解决死锁的情况。然后,我们将实现自己的高性能无争用共享互斥体,它比标准速度快得多std::shared_mutex。并且,在此基础上,我们将创建安全指针的优化版本safe_ptr,名为contfree_safe_ptr<>。最后,我们将运行性能测试,同时与无锁库libCDS进行比较。通过的示例,我们将看到接近libCDS(SkipListMap 和BronsonAVLTreeMap)的性能contfree_safe_ptr<std::map<>>。

结果,为了使您的任何类线程安全,

contfree_safe_ptr ptr_thread_safe;
  性能几乎与您在之前的类方法中开发无锁算法一样。另外,有可能一次更改多个contfree_safe_ptr <>。除了std :: shared_ptr <>,我们的智能指针还将包含一个引用计数。可以复制它,并在删除最后一个副本后,将自动释放动态分配的内存。

最后,将提供1个safe_ptr.h文件,该文件足以通过#include“ safe_ptr.h”进行连接,以使您可以使用此类。

多线程数据交换的基本原理

如您所知,只有在以下4种情况下,我们才能从不同的线程读取和更改同一对象:

基于锁。该对象受锁保护:自旋锁,std(互斥锁,recursive_mutex,timed_mutex,shared_timed_mutex,shared_mutex …):http 😕/en.cppreference.com/mwiki/index.php?title=Special%3ASearch&search=mutex
  原子的。该对象的类型为std :: atomic ,其中T是指针,布尔型或整数类型(std :: is_integral :: value == true),并且仅当T类型存在原子操作时在CPU级别:http : //en.cppreference.com/w/cpp/atomic/atomic (2 + 1)-(Lock-based-Atomic)否则,如果T类型是平凡可复制的类型,即满足条件std :: is_trivially_copyable :: value == true,然后std :: atomic 用作基于锁的-锁会自动在其中使用。
  交易安全。为与对象一起工作而实现的功能可提供线程安全保证transaction_safe(事务内存TS(ISO / IEC TS 19841:2015)-实验性C ++):http : //en.cppreference.com/w/cpp/ 语言/交易记忆
  无锁。用于对象的功能是在无锁算法的基础上实现的,即它们提供了无锁线程安全保证。
  如果了解确保线程安全的所有4种方法,则可以跳过本章。

让我们考虑相反的顺序:

(4)无锁算法非常复杂,创建每种复杂算法通常需要数项计算工作。可在容器中使用的无锁算法示例-unordered_map,ordered_map,队列等等…

这些是非常快速且可靠的多线程数据结构

(3)交易安全性计划包含在C ++标准的实验部分中,并且已经在以下位置提供了ISO / IEC TS 19841:2015草案:http : //www.open-std.org/jtc1/sc22 /wg21/docs/papers/2015/N4514.pdf。

但是,即使并非所有STL容器都计划成为交易安全的。例如,甚至没有计划将std :: map容器设置为事务安全的,因为仅将以下功能定义为事务安全的:begin,end,size,max_size,空。但是以下函数未定义为线程安全的:查找,修改,插入。而且,使用事务安全的成员函数来实现自己的对象根本不容易,否则可以使用std :: map来实现。

(2)原子。这种方法已经在C ++ 11中进行了标准化,您可以轻松地使用它。例如,声明变量std :: atomic shared_val,然后在多个线程中传递指向其的链接或指针就足够了,并且通过成员函数和运算符std :: atomic进行的所有调用都是线程安全的: [3] http://coliru.stacked-crooked.com/a/9d0219a5cfaa3cd5。

成员功能,专业成员功能:http : //en.cppreference.com/w/cpp/atomic/atomic

重要的是要理解,如果执行几个原子变量操作(这无关紧要,如果它发生在一个表达式或多个表达式中),则它们之间的另一个线程可以更改该变量的值。因此,对于几个操作的原子执行,我们使用基于CAS函数(Compare-And-Swap)compare_exchange_weak()的无锁方法-即:我们将原子变量中的值读入局部变量(old_local_val),执行许多必要的操作并将结果写入局部变量(new_local_val),最后,我们通过CAS函数将原子变量的当前值与初始值(old_local_val)进行比较;如果它们不相等,那么我们重新执行循环,如果它们相等,则表示在此期间另一个线程未做任何更改,然后我们用新值(new_local_val)更改了原子变量值。那时,比较和赋值是通过一个操作完成的:compare_exchange_weak()-它是一个原子函数,在完全执行之前,没有人可以更改变量的值:[4]http://coliru.stacked-crooked.com/a/aa52b45150e5eb0a。

这种带有循环的方法称为乐观锁。悲观锁有:自旋锁,互斥锁…

而且,如果在没有悲观锁的情况下执行了该循环的所有操作,则这种算法称为无锁。更准确地说,这种算法可以保证:无障碍,无锁,无等待或无写入。

通常,原子CAS函数会替换指针,即:分配新的内存,将修改后的对象复制到该内存,并获得指向该内存的指针;在此副本上执行许多操作,最后,如果在这段时间内减肥食谱:www.sheonline.cn 另一个线程未更改旧指针,则CAS函数将旧指针替换为新对象指针。但是,如果指针已被另一个线程更改,则将再次重复所有操作。

所谓的“ABA”一个可能的问题可能会出现在这里-

https://en.wikipedia.org/wiki/ABA_problem

当其他线程有时间两次更改指针,然后指针第二次更改为初始值时,但是在该地址,其他线程已经能够删除该对象并创建一个新对象。也就是说,指针的值已经指示了另一个对象,但是我们看到该值没有改变,并认为该对象尚未被替换。解决此问题的方法有很多,例如:LL / SC,RCU,hazard_pointer,垃圾收集器…

原子是在线程之间交换数据的最快方法。此外,对于所有原子操作,可以使用不太严格和更快的存储保护,这将在后面详细讨论。默认情况下,使用最安全和严格的重新排序障碍:std :: memory_order_seq_cst。但是如上所述,您需要付出很多努力才能通过使用原子变量来实现复杂的逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值