File tree Expand file tree Collapse file tree 1 file changed +60
-1
lines changed Expand file tree Collapse file tree 1 file changed +60
-1
lines changed Original file line number Diff line number Diff line change @@ -777,8 +777,67 @@ void recursiveFunction(int count) {
777
777
778
778
> [ 运行] ( https://godbolt.org/z/6YTxzM8fj ) 测试。
779
779
780
+ ## ` new ` 、` delete ` 是线程安全的吗?
781
+
782
+ 如果你的标准达到 ** C++11** ,要求下列** 函数** 是线程安全的:
783
+
784
+ - [ ** ` new ` 运算符** ] ( https://zh.cppreference.com/w/cpp/memory/new/operator_new ) 和 [ ` delete ` 运算符] ( https://zh.cppreference.com/w/cpp/memory/new/operator_delete ) 的** 库** 版本
785
+ - 全局 ** ` new ` 运算符** 和 ` delete ` 运算符的用户替换版本
786
+ - [ std::calloc] ( https://zh.cppreference.com/w/cpp/memory/c/calloc ) 、[ std::malloc] ( https://zh.cppreference.com/w/cpp/memory/c/malloc ) 、[ std::realloc] ( https://zh.cppreference.com/w/cpp/memory/c/realloc ) 、[ std::aligned_alloc] ( https://zh.cppreference.com/w/cpp/memory/c/aligned_alloc ) (C++17 起)、[ std::free] ( https://zh.cppreference.com/w/cpp/memory/c/free )
787
+
788
+ 所以以下函数在多线程运行是线程安全的:
789
+
790
+ ``` cpp
791
+ void f (){
792
+ T* p = new T{};
793
+ delete p;
794
+ }
795
+ ```
796
+
797
+ 内存分配、释放操作是线程安全,构造和析构不涉及共享资源。而局部对象 ` p ` 对于每个线程来说是独立的。换句话说,每个线程都有其自己的 ` p ` 对象实例,因此它们不会共享同一个对象,自然没有数据竞争。
798
+
799
+ 如果 ` p ` 是全局对象(或者外部的,只要可被多个线程读写),多个线程同时对其进行访问和修改时,就可能会导致数据竞争和未定义行为。因此,确保全局对象的线程安全访问通常需要额外的同步措施,比如互斥量或原子操作。
800
+
801
+ ``` cpp
802
+ T* p = nullptr ;
803
+ void f (){
804
+ p = new T{}; // 存在数据竞争
805
+ delete p;
806
+ }
807
+ ```
808
+
809
+ 即使 ` p ` 是局部对象,如果构造函数(析构同理)涉及读写共享资源,那么一样存在数据竞争,需要进行额外的同步措施进行保护。
810
+
811
+ ``` cpp
812
+ int n = 1 ;
813
+
814
+ struct X {
815
+ X (int v){
816
+ ::n += v;
817
+ }
818
+ };
819
+
820
+ void f(){
821
+ X* p = new X{ 1 }; // 存在数据竞争
822
+ delete x;
823
+ }
824
+ ```
825
+
826
+ ---
827
+
828
+ 值得注意的是,如果是自己重载 `operator new`、`operator delete` 替换了库的**全局**版本,那么它的线程安全就要我们来保证。
829
+
830
+ ```cpp
831
+ // 全局的 new 运算符,替换了库的版本
832
+ void* operator new (std::size_t count){
833
+ return ::operator new(count);
834
+ }
835
+ ```
836
+
837
+ 以上代码是线程安全的,因为 C++11 保证了 new 运算符的库版本,即 ` ::operator new ` 是线程安全的,我们直接调用它自然不成问题。如果你需要更多的操作,就得使用互斥量之类的方式保护了。
838
+
780
839
## 总结
781
840
782
- 本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(` std::mutex ` )保护共享数据,并且要注意互斥量上锁的“** 粒度** ”。C++标准库提供了很多工具,包括管理互斥量的管理类(` std::lock_guard ` ),但是互斥量只能解决它能解决的问题,并且它有自己的问题(** 死锁** )。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 ` std::call_once() ` 保护共享数据的初始化过程,使用读写锁(` std::shared_mutex ` )保护不常更新的数据结构。以及特殊情况可能用到的互斥量 ` recursive_mutex ` ,有些人可能喜欢称作:** 递归锁** 。
841
+ 本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(` std::mutex ` )保护共享数据,并且要注意互斥量上锁的“** 粒度** ”。C++标准库提供了很多工具,包括管理互斥量的管理类(` std::lock_guard ` ),但是互斥量只能解决它能解决的问题,并且它有自己的问题(** 死锁** )。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 ` std::call_once() ` 保护共享数据的初始化过程,使用读写锁(` std::shared_mutex ` )保护不常更新的数据结构。以及特殊情况可能用到的互斥量 ` recursive_mutex ` ,有些人可能喜欢称作:** 递归锁** 。最后聊了一下 ` new ` 、 ` delete ` 运算符的库函数实际是线程安全的,以及一些问题。
783
842
784
843
下一章,我们将开始讲述同步操作,会使用到 [ ** Futures** ] ( https://zh.cppreference.com/w/cpp/thread#.E6.9C.AA.E6.9D.A5.E4.BD.93 ) 、[ ** 条件变量** ] ( https://zh.cppreference.com/w/cpp/thread#.E6.9D.A1.E4.BB.B6.E5.8F.98.E9.87.8F ) 等设施。
You can’t perform that action at this time.
0 commit comments