线程安全vs可重入
线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。(线程在执行中的相互关系)
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就会有其他执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。(函数的特点)
引起线程安全的情况有很多种,函数不可重入,是引起线程安全情况的一种常见情况。
可重入函数一定是线程安全的
常见不可重入情况
调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
调用了标准I/O库函数,标准I/O库函数的很多实现都以不可重入方式使用全局数据结构
可重入函数体内使用了静态的数据结构
死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。
死锁是多线程把锁不合理的使用,导致代码不会继续向后正常推进。
例子:(请求与保持条件)
线程A申请锁的顺序是先1后2,线程B申请锁的顺序是先2后1,结果是各自拿到锁后,都无法拿到另一把锁。

死锁的四个必要条件(死锁必须满足以下条件)
互斥条件 :一个资源每次只能被一个执行流使用
请求与保持条件 :一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件 :一个执行流已获得的资源,在未使用前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
解决,避免死锁条件必定是破坏这4个条件之一。
- 互斥条件------不用锁(比如访问只读的临界资源)。
- 请求与保持条件------不要保持,当线程申请失败了,对已获得的锁(资源)进行释放
pthread_mutex_trylock。 - 不剥夺条件------可以理解为把其他线程的锁给解锁了,"抢过来"。
此外,还有
- 申请多把锁时,每个线程申请锁的顺序应该一致。
- 避免锁未释放的场景
- 资源一次性分配
STL是否是线程安全的
不是
原因是STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。
而且对于不同的容器,加锁方式的不同,性能可能也不同。
因此,STL默认不是线程安全的。如果在多线程环境下使用,需要调用者自行保证线程安全。
智能指针是否是线程安全的?
对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。
对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现时考虑到了这一点,基于原子操作的方式保证shared_ptr能够高效,原子操作引用计数。
线程安全的单例模式
某些类,只应该具有一个对象(实例),就称之为单例
懒汉实现方式和饿汉实现方式
在单例模式中,饿汉式和懒汉式是两种最核心的实现方式,二者的核心差异在于实例化的时机:饿汉式在类加载时就创建实例,懒汉式则在第一次使用时才创建实例。
饿汉方式实现单例模式
利用全局静态变量 / 类内静态成员变量的初始化时机(程序启动时,全局 / 静态变量在main函数执行前完成初始化),提前创建单例实例。
c
template <typename T>
class Singleton
{
static T data;
public:
static T *GetInstance()
{
return &data;
}
}
实例在main前初始化,若实例依赖其他全局变量,可能出现初始化顺序问题
无法实现懒加载,初始化耗时可能影响程序启动速度
懒汉方式实现单例模式
懒汉方式最核心的思想是"延迟加载",从而能够优化服务器的启动速度。
延迟创建实例:只有第一次调用getInstance()时才创建实例。
c
template <typename T>
class Singleton
{
static T *inst;
public:
static T *GetInstance()
{
if (inst == NULL)
{
inst = new T();
}
return inst;
}
}
自旋锁

在临界区内线程执行时长的问题
如果时间比较久:推荐其他线程挂起等待。(互斥锁)
如果时间比较短:推荐其他线程不要休眠阻塞,而是不断一直抢占锁,直到申请成功。(自旋)
pthread_spin_destroy
pthread_spin_init
pthread_spin_lock
pthread_spin_unlock
读者写者问题
读者写者问题:有线程向公共资源中写入,其他线程从公共资源中读取数据
读者写者问题最常见的情况------读者众多,写者较少
321原则
3:三种关系:写者与写者(互斥),读者与写者(互斥&&同步),读者与读者(没有关系)
2:两个对象:读者,写者
1:一个交易场所
读者写者问题vs生产消费问题
本质区别是读者与消费者的区别
消费者:会把数据拿走。
读者:只读,只会拷贝!
理解,伪代码模拟实现一下读写者的加锁逻辑

系统中读写锁的接口
pthread_rwlock_destroy
pthread_rwlock_init
pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_unlock
其他问题
1.读者优先
2.写者优先
pthread_rwlock_t:默认就是读者优先的,会有写者饥饿问题