Linux线程互斥
临界资源:多线程执行流共享的资源
临界区:访问临界资源的代码
互斥:任何时刻,有且只有一个执行流进入临界区,访问临界资源,对临界资源起保护作用
原子性:不会被任何调度机制打断的操作
互斥量mutex
互斥量初始化
- 静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 动态分配:int pthread_mutex_int(&mutex, NULL)
互斥量销毁
int pthread_mutex_destroy(&mutex);
静态分配的互斥量不用销毁,已经加锁的互斥量不能销毁,销毁后的互斥量不会被线程尝试加锁
互斥量加锁解锁
- int pthread_mutex_lock(&mutex);
- int pthread_mutex_unlock(&mutex);
如果互斥量处于已锁状态,或者没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁,再次申请。
互斥量原理
互斥锁加锁是原子的,锁本身就是临界资源,锁本身也需要保护,系统内部采用一条汇编指令,swap或者exchange,功能是交换内存和寄存器里的值,从而保证原子性。解锁不是原子的。
伪代码:
lock:
movb $0, %al//把0赋值给寄存器
xchgb %al, mutex//把锁跟al交换,锁=0,al=1
if(al寄存器的内容 > 0)
{
return 0;
}
else
{
挂起等待
goto lock;
}
unlock:
movb $1, mutex;
return 0;
将mutex跟自己的私有的数据进行了交换,1只有1份。
可重入VS线程安全
重入:一个函数被多个执行流重复进入,前一个执行流还没有执行完,就有其他执行流再次进入。在重入的情况下,如果运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则就是不可重入函数。
线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操 作,并且没有锁保护的情况下,会出现该问题。
常见的线程不安全的情况:
- 不保护共享变量;
- 函数状态随着被调用,状态发生变化;一个函数中有一个static变量,一个执行流在改变这个变量,而另一个执行流在用这个变量做判断;
- 返回指向静态变量指针的函数;
- 调用线程不安全函数的函数;
常见的线程安全情况:
- 对全局变量或静态变量只有读权限;
- 类或者接口对于线程来说都是原子操作;核心代码都是原子的
- 多个线程之间的切换不会导致改接口的执行结果存在二义性;
常见的不可重入的情况:
调用了malloc/free函数,,因为malloc函数是用全局链表来管理堆的 ,链表操作时不可重入的;
常见的可重入情况:
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据,要使用全局数据时,拷贝一份到本地,再对拷贝的数据进行操作。
可重入与线程安全的联系:
- 可重入就是线程安全的;
- 不可重入就不是线程安全的;
- 有全局变量,既不是可重入的,也不是线程安全的
可重入与线程安全的区别:
- 线程安全针对线程,重入针对函数;
- 可重入是线程安全的一种;
- 线程安全不一定是可重入的,可重入一定是线程安全的;
- 线程安全的问题可以通过加锁解决,重入的问题加锁不一定能解决。
死锁
一组进程的进程均占有不会释放的资源,相互申请被其他进程占有的不会释放的资源而处于一种永久等待的状态。
多线程申请锁时,申请锁的顺序、方式不妥当,而导致多个执行流当中的一个或多个执行流不能够继续推进的情况。导致程序无作为。
死锁的四个必要条件:
- 互斥条件,一个资源值能被一个执行流使用;
- 请求与保持条件,因请求被阻塞时,对已获得的资源保持不放;
- 不剥夺条件: 一个执行流申请到锁时,如果不释放,其他执行流不能剥脱这把锁
- 循环与等待:若干执行流形成一种头尾相接的资源等待关系
避免死锁的方式:
- 破坏死锁的四个必要条件:除了第一条
- 加锁顺序一致:拿到一锁,就能拿到二锁
- 避免锁未释放的场景
- 资源一次性分配
避免死锁算法
- 死锁检测算法
- 银行家算法
同步
保证数据安全的前提下,线程按照某种特定的顺序来访问临界资源,从而有效避免饥饿问题。引入条件变量。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。
条件变量用来同步,
条件变量接口:
int pthread_cond_init(q&cond, NULL);
int pthread_cond_destroy(&cond);
int pthread_cond_wait(&cond,&mutex);//释放锁,进入等待队列,mutex中有一个队列,条件变量下也有一个队列,如果不释放就会造成死锁问题。
int pthread_cond_broadcast(&cond);//广播唤醒所有在该条件变量下等待的线程
int pthread_cond_signal(&cond);//通知一个,线程醒来即拿着锁。
条件变量使用规范:
pthread_mutex_lock(&mutex);
while(条件为假)//此时使用while循环,wait函数调用失败,if语句就会直接往后执行,但是此时条件可能并不满足。
pthread_cond_wait(cond, mutex);
pthread_mutex_unlock(&mutex);
消费者生产者模型
POSIX信号量,可以用于线程间同步
int sem_init(sem_t *sem, int pshared, int value);第二个参数是0,表示线程间共享,非0表示进程间共享
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);等待信号量,会将信号量的值-1,P操作
int sem_post(sem_t *sem);发布信号量,资源使用完毕,归还资源,信号量+1,V操作
读写锁
当前锁状态 | 读锁请求 | 写锁请求 |
无锁 | 可以 | 可以 |
读锁 | 可以 | 阻塞 |
写锁 | 阻塞 | 阻塞 |
代码:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int book = 0;
pthread_rwlock_t rwlock;
void* Reader(void* arg)
{
for(;;)
{
pthread_rwlock_rdlock(&rwlock);
cout<<"read book: "<<book<<endl;
pthread_rwlock_unlock(&rwlock);
usleep(300000);
}
}
void* Writer(void* arg)
{
for(;;)
{
pthread_rwlock_wrlock(&rwlock);
book++;
pthread_rwlock_unlock(&rwlock);
cout<<"write book: "<<book<<endl;
sleep(2);
}
}
int main()
{
pthread_rwlock_init(&rwlock, NULL);
pthread_t r,w;
pthread_create(&r,NULL,Reader,NULL);
pthread_create(&w,NULL,Writer,NULL);
pthread_join(r,NULL);
pthread_join(w,NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}