一. 概念:
1. 同步和锁:
1. 二元信号量:最简单的一种锁;只有占用或者没有占用;他适合唯一一个线程独占的资源,一旦被占用,其他所有线程将会等待直到锁释放;
2. 多元信号量:简称信号量,允许多个线程并发访问的资源
3. 互斥量:与二元信号量很相似,资源仅同时允许一个线程访问,但是有所不同,**信号量可以在一个线程获取并且阿紫另一个线程释放,而互斥量必须试获取的线程才能释放**
4.临界区: 比互斥量更严格的同步手段。**互斥量和信号量在系统的任何进程都可以看见,临界区的范围只在本进程可见,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量** 。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
5.读写锁:对同一个锁,读写锁有两种获取方式,共享和独占
读写锁状态 | 共享方式获取 | 独占方式获取 |
---|
自由 | 成功 | 成功 |
共享 | 成功 | 等待 |
独占 | 等待 | 等待 |
6.条件变量:对于条件变量,线程可以有两种操作,可以等待条件变量,一个条件可以被多个线程等待;线程也可以唤醒条件变量,此时某个或所有等待此条件变量的线程将被唤醒并持续支持。**条件变量可以让多线程等待同一个事件发生,当事件发生,所有的线程又可以一起恢复**
2.可重入与线程安全:
1.一个函数可被重入:
1).多个线程同时执行该函数不会出现不良后果;
2).函数可以自己调用自己;
3.过度优化:
原因:由于编译器优化,提高访问速度,可能会将某些变量放入线程寄存器里,然而线程寄存器是相互独立;又或者编译的顺序优化毫无相干的两条指定导致执行顺序变化;
x可能依旧为1
```c++
x= 0
Thread1 Thread2
lock() lock()
x++ x++
unlock() unlock()
```
···
x=y=0
Thread1 Thread2
x=1 y=1
r1 =y r2=x
···
r2 = r1= 0 是可能的 当实行顺序发生了如下的改变
```
x=y=0
Thread1 Thread2
r1 = y y=1
x=1 r2=x
```
1.volatile关键字阻止过度优化
1).阻止编译器将变量缓存在寄存器内而不写回内存
2).阻止编译器调整操作**volatile变量**的指令顺序
2.关于单例模式的指令换顺序
```
volatile T* p = 0;
T * GetInstance(){
if (p == NULL)
{
lock();
if( p ==NULL){
p = new T;
}
unlock();
}
}
return p;
}
```
这样的代码看似没有问题,但是new有两个步骤组成而赋值就有三个步骤:
(1).分内存(malloc)
(2).在内存位置调用构造函数
(3).将内存地址赋值给p
在这3个步骤中,后两个步骤是可以顺序颠倒的,也就是说已经分配了内存,但是却没有构造完成,如果这时有另一个线程调用,那么p将不为null,将返回未构造完成的p的指针回去,就会导致程序的崩溃;
解决方法: **调用barrier指令,防止在barrier的指令交换到barrier之后执行;**
```
volatile T* p = 0;
T * GetInstance(){
if (p == NULL)
{
lock();
if( p ==NULL){
T* tmp = new T;
barrier();
p = tmp;
}
unlock();
}
}
return p;
}
```