OS系列文章目录
- 进程、线程
- 并发,同步
- 死锁
- 内存管理
- 虚拟内存
- 文件系统
- IO和存储
前言
这一节主要讨论的是多线程下的问题和解决这类问题的机制,关于多线程机制的使用会准备专门的含有代码的博客
一、多线程的问题
在多线程并发执行的情况下,由于同一个进程中不同线程共享同一片虚拟内存,这个时候某个进程对堆栈内的变量进行修改操作会导致其它线程(或者说整个进程)无法正确执行,这一部分资源叫做临界区,这种特性叫做异步性,根本原因在于非原子化的操作导致线程推进过程中会遇到遭遇CPU线程调度切换。 要保证多线程情况下进程正确推进,我们必须同步线程的执行,使它们总是有一个安全的轨迹。也就是说,我们要保证互斥地访问每个临界区。
二、解决的机制
信号量(Semaphores)
信号量S具有非负整数值的全局变量,被P和V操作处理
-
P(s):wait()
- 如果s非零,那么P将s减1并立即返回
- 如果s为零,那么就挂起这个线程,直到s变为非零,等待一个V操作重启这个线程
- 重启后,P操作将s减1,将控制权转移给调用者
-
V(s):signal()
- 将s加1
- 如果有任何线程阻塞在P操作等待s变成非零,那么V操作将会重启一个线程,然后将s减1来完成它的P操作
原子操作即从机制上保证在执行过程中是无法被外部或者内部中断的,要么操作全部执行,要么不执行
锁(lock)
锁其实是作为一种临界资源的限制条件,实际上就是一种数据结构,只有当线程得到锁之后可以继续推进,否则需要等其它占用锁的线程释放才可占用,从而实现互斥
- lock(&mutex):尝试得到锁,如果无法得到会进入休眠
- unlock(&mutex):释放锁
lock_t mutex; // some globally-allocated lock ‘mutex’
lock(&mutex);
balance = balance + 1;
unlock(&mutex)
spinlock数据结构和对应操作
typedef struct __lock_t { int flag; } lock_t;
void init(lock_t *mutex) {
// 0 à lock is available, 1 à held
mutex->flag = 0;
}
void lock(lock_t *mutex) {
while (mutex->flag == 1) // TEST the flag
; // spin-wait (do nothing)
mutex->flag = 1; // now SET it !
}
void unlock(lock_t *mutex) {
mutex->flag = 0;
}
条件变量(Condition variable)
Condition variable 本质上是一个队列及对该队列的操作原语
三、线程不安全函数
一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产
生正确的结果
四类线程不安全函数:
- 不保护共享变量的函数
- 保持跨越多个调用的状态的函数
- 返回指向静态变量的指针的函数:正在被一个线程使用的结果可能会被另一
个线程悄悄地覆盖了 - 调用线程不安全函数的函数