多线程编程的常见问题
目录
1. 线程安全和可重入函数问题
2. 死锁的理解
2.1 死锁的概念
2.2 死锁的四个必要条件
3. C++中STL容器的线程安全问题
4. C++中智能指针的线程安全问题
1. 线程安全和可重入函数问题
线程安全:线程安全是指在多线程环境下,一个函数或者一段代码可以被多个线程同时调用,并且能够正确地处理共享资源,不会因为多线程的并发执行而产生数据竞争、不一致或者其他未定义行为。
可重入函数:可重入函数是指一个函数可以被多个线程或者同一个线程在不同的执行上下文中安全地调用,即使在函数执行过程中被中断并再次被调用,也不会出现数据混乱或者其他错误。可重入函数不依赖于任何静态或全局的非常量数据,也不修改任何静态或全局的非常量数据,所有数据都由函数的调用者提供。
可重入性与线程安全的联系:
1. 目标一致:线程安全和可重入函数的主要目标都是为了确保在多线程环境下程序的正确性和稳定性。它们都致力于避免因多线程并发执行而导致的数据竞争和不一致问题。
2. 可重入性是线程安全的充分条件:如果一个函数是可重入的,那么它通常是线程安全的。因为可重入函数不依赖于静态或全局的非常量数据,多个线程同时调用该函数时不会出现数据竞争的情况。
可重入性与线程安全的区别:
1. 可重入函数不依赖互斥同步机制,通过避免使用静态或全局的非常量数据来实现线程安全,是线程安全的一种。
2. 线程安全不一定是可重入的,而可重入函数一定是线程安全的。如果现在是单进程单线程程序,一个函数对临界资源的访问加了锁,则这个函数是线程安全的,但是在这个函数执行的过程中由于信号中断或其他情况再次进入该函数,此时锁还未释放,就会产生死锁问题,因此该函数是不可重入的。
知识点1:
线程安全说明的是线程访问公共资源的安全情况,表现的是并发线程的特点。可重入描述的是一个函数是否能被重复进入,表示的是函数的特点。
2. 死锁的理解
2.1 死锁的概念
死锁是指在多线程或多进程环境下,两个或多个进程(线程)因争夺资源而造成的一种互相等待的僵持局面。例如,进程 A 占用了资源 R1,进程 B 占用了资源 R2,而此时进程 A 又请求资源 R2,进程 B 请求资源 R1,两个进程相互等待对方释放资源,从而形成死锁。
单线程也可能产生死锁的情况,比如在执行一个函数的时候,对一把锁申请两次,而第二申请时已经持有锁了,申请就会处于阻塞状态。
2.2 死锁的四个必要条件
1. 互斥条件:资源在某一时刻只能被一个执行流所占有。
2. 请求和保持条件:一个执行流已经持有至少一个资源,但是又提出新的资源请求,而新请求的资源被其他执行流占用,此时请求的执行流会保持自己已有的资源,同时等待新资源。
3. 不可剥夺条件:一个执行流获得的资源在未使用完之前,不能被其他执行流强行剥夺,只能由当前持有该资源的执行流自己释放。
4. 循环等待条件:存在一组执行流,它们之间形成了一个循环等待链。例如,进程 A 占用了资源 R1,进程 B 占用了资源 R2,而此时进程 A 又请求资源 R2,进程 B 请求资源 R1,两个进程相互等待对方释放资源。
3. C++中STL容器的线程安全问题
在 C++ 里,标准模板库(STL)容器本身并非线程安全的。STL 的设计初衷是追求高效性和通用性,而线程安全会带来额外的性能开销,比如使用互斥锁等同步机制会增加锁的获取和释放操作,这会降低容器的操作速度。所以,为了让开发者在不需要线程安全时能获得更好的性能,STL 容器默认不提供线程安全的保障。
4. C++中智能指针的线程安全问题
C++ 中的智能指针是否线程安全不能一概而论,需要根据具体的智能指针类型来分析。
std::unique_ptr
是一种独占式智能指针,同一时间只能有一个 std::unique_ptr
指向某个对象。从其设计特性来看,std::unique_ptr
本身并不涉及线程安全问题,因为它不允许多个 std::unique_ptr
同时管理同一个对象,也就不存在多个线程同时操作同一个 std::unique_ptr
指向的资源的情况。
std::shared_ptr
是一种共享式智能指针,多个 std::shared_ptr
可以指向同一个对象,并且会维护一个引用计数,当引用计数为 0 时,对象会被自动删除。std::shared_ptr
的引用计数操作是线程安全的。标准库保证了对引用计数的递增和递减操作是原子的,这意味着多个线程可以同时对同一个 std::shared_ptr
进行拷贝、赋值或销毁操作,而不会出现引用计数混乱的问题。虽然引用计数操作是线程安全的,但对 std::shared_ptr
指向的对象的访问不是线程安全的。如果多个线程同时访问和修改 std::shared_ptr
指向的对象,需要使用同步机制(如互斥锁)来保证线程安全。