Skip to content

Commit 5733a17

Browse files
committed
在共享数据中更新一节 “newdelete 是线程安全的吗?”
1 parent 4480277 commit 5733a17

File tree

1 file changed

+60
-1
lines changed

1 file changed

+60
-1
lines changed

md/03共享数据.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,67 @@ void recursiveFunction(int count) {
777777
778778
> [运行](https://godbolt.org/z/6YTxzM8fj)测试。
779779
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+
780839
## 总结
781840
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` 运算符的库函数实际是线程安全的,以及一些问题。
783842
784843
下一章,我们将开始讲述同步操作,会使用到 [**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)等设施。

0 commit comments

Comments
 (0)