互斥量、应用函数、加锁解锁、死锁、读写锁
条件变量、信号量、互斥量实现进程间同步、
互斥量mutex
- linux 中提高一把互斥锁mutex(称之为互斥量)
- 每个线程在对资源操作前尝试先加锁,成功加锁才能,操作结束解锁。
资源还是共享,线程间也还是竞争,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生。
注意:同一时刻,只能有一个线程持有该锁。
当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,但出现数据混乱。所以互斥锁实质上是操作系统提高一把“建议锁”(协同锁),建议程序中有多线程访问共享资源的时候使用该机制。但并没有强制限定。
主要应用函数
pthread_mutex_init();
pthread_mutex_destroy();
pthread_mutex_lock();
pthread_mutex_trylock();//轮询方式加锁
pthread_mutex_unlock();
//函数返回值,成功:0,失败返回错误号
pthread_mutex_t 类型,其本质是一个结构体。可简化理解,应用时忽略其实现细节,简单当成整数看待。
pthread_mutex_t mutex;//变量mutex只有两种值1,0。初始化成功为1
pthread_mutex_init函数
初始化一个互斥锁(互斥量),初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//参1:传出参数,调用时应传&mutex
//参2:互斥量属性,一个传入参数,通常传NULL,选用默认属性(线程间共享)
restrict关键字:只限用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或者指针修改。
静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可直接使用宏进行初始化。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化
pthread_mutex_init(&mutex,NULL);
pthread_mutex_destory函数
销毁一个互斥锁
int pthread_mutex_destory(pthread_mutex_t *mutex);
pthread_mutex_lock函数
加锁,可理解为将mutex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock函数
解锁,可理解为将mutex++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_trylock函数
尝试加锁,(解锁时不会阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);
加锁和解锁
lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直到T1解锁。T1解锁后,T2,T3,T4均被唤醒,并自动再次尝试加锁。可假想mutex锁init成功初值1,lock功能是将mutex--,unlock将mutex++。
lock加锁失败会阻塞,等待锁释放;
trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。
加锁测试 【mutex.c】
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex;
void *tfn(void *arg){
srand(time(NULL));
while(1){
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand()%3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
printf("world\n");
sleep(rand()%3);
}
return NULL;
}
int main(){
int flg = 5;
pthread_t tid;
pthread_mutex_init(&mutex,NULL);//初始化一个互斥锁
pthread_create(&tid,NULL,tfn,NULL);//创建一个线程
while(flg--){
pthread_mutex_lock(&mutex);//给互斥锁加锁
printf("HELLO");
sleep(rand()%3);
printf("WORLD\n");
pthread_mutex_unlock(&mutex);//给互斥锁解锁
sleep(rand()%3);
}
pthread_cancel(tid);//杀死线程
pthread_join(tid,NULL);//阻塞等待线程退出,获取线程退出状态
pthread_mutex_destory(&mutex);//销毁互斥锁
return 0;
}
该程序由于没有共享和竞争而没有加任何同步机制,导致产生时间有关的错误,造成数据混乱。
【练习】修改上述程序,使用mutex互斥锁进行同步。
- 定义全局互斥量,初始化init(&m,NULL)互斥量,添加对应的destory
- 两个线程while中,两次printf前后,分别加lock和unlock
- 将unlock挪至第二个sleep后,发现交替现象很难出现。线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠,睡醒解锁后有立即加锁,这两个库函数本身不会阻塞。所以在两行代码之间失去CPU的概率很小,因此,另一个线性很难得到加锁机会。
- main中加flag=5将flg在while中--,这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。
- main中加pthread_cancel()将子线程取消。
【pthrd_mutex.c】
结论:在访问共享资源前加锁,访问结束后立即加锁。锁的“粒度”应越小越好。
死锁
- 线程试图对同一个互斥量A加锁两次
- 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁。
【作业】编写程序,实现上述两种死锁现象
读写锁
与互斥量类似,但读写锁允许更高的并行性,其特性为:写读占,读共享。
读写锁状态
一把读写锁具备三种状态(读写是一把锁,两种状态)
- 读模式下加锁状态(读锁)
- 写模式下加锁状态(写锁)
- 不加锁状态
读写锁特性
- 读写锁是“写模式式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞
- 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
- 读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程,那么读写锁会阻塞随后的读模式锁请求,优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高。
读写锁叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住;当它以写模式锁住时,它是以独占模式锁住的,写独占,读共享。
读写锁非常合适与对数据结构读的次数远大于写的情况。
主要应用函数
pthread_rwlock_init函数
用于初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
//参数2:attr表速写锁属性,通常使用默认属性,传NULL即可
pthread_rwlock_destory函数
销毁一把读写锁
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock函数
用于以读写式请求读写锁(又称:请求读锁)
int pthread_rwlock_rdlock(pthread_rwkock_t *rwlock);
pthread_rwlock_wdlock函数
以写方式请求读写锁(请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
pthread_rwlock_unlock函数
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock函数
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_trywrlock函数
非阻塞以写方式请求读写锁(非阻塞请求写锁)
读写锁例子:同时有多个线程对同一个全局数据读、写操作、【rwlock.c】
条件变量
条件变量本身不是锁,但它可以造成线程阻塞,通常与互斥锁配合使用。给多线程提供一个会合的场所。
主要应用函数
pthread_cond_init 函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//参数2:attr表条件变量属性,通常默认值,传NULL即可
//用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
作用:
- 阻塞等待条件变量cond(参1)满足
- 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
- 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex(&mutex)
pthread_cond_timedwait函数
限时等待一个条件变量
int pthread_cond_timewait(pthread_cond_t *restrict,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
//参3:为一个结构体 sturct timespec{time_t tv_sec; long tv_nsec;}
形参abstime:绝对时间
如:time(NULL)返回为绝对时间,而alarm(1)是相对时间,相对当前时间定时1秒钟。
time_t cur = time(NULL);//获取当前时间
struct timespec t;//定义timespec结构体变量t
t.tv_sec = cur+1;//定时1秒
pthread_cond_timewait(&cond,&mutex,&t);//传参
用餐模型分析
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
struct msg{
int num;
struct msg *next;
};
struct msg *head = NULL;
struct msg *mp = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
void *producter(void *arg){
while(1){
mp = malloc(sizeof(struct msg));
mp->num = rand()%400+1;
printf("---producted -- %d\n",mp->num);
pthread_mutex_lock(&mutex);
mp->next = head;//此时mp变为头结点(在head之前插入)
head = mp;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product);
sleep(rand()%3);
}
return NULL;
}
void *consumer(void *arg){
while(1){
pthread_mutex_lock(&mutex);
while(head == NULL){
pthread_cond_wait(&has_product,&mutex);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&mutex);
printf("--consumer --%d\n",mp->num);
free(mp);
mp=NULL;
sleep(rand()%3);
}
return NULL;
}
int main(void){
pthread_t ptid,ctid;
pthread_create(&ptid,NULL,producter,NULL);//线程号,线程属性(NULL为默认),回调函数,传入参数
pthread_create(&ctid,NULL,consumer,NULL);
pthread_join(ptid,NULL);
pthread_join(ctid,NULL);
return 0;
}
asd
asdf
哲学家用餐模型
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>
#include<sys/mman.h>
#include<sys/wait.h>
int main(){
//创建5个进程
//创建5只筷子作为锁
//实现强筷子吃面
int i;
pid_t pid;
sem_t *s;
s = mmap(NULL,sizeof(sem_t)*5,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//映射区?
if(s == MAP_FAILED){
perror("fail to mmap");
exit(1);
}
for(i=0;i<5;i++){
sem_init(&s[i],0,1);//信号量 ,信号在各进程间是否共享,信号量初值1,(信号量变为互斥锁)
}
for(i=0;i<5;i++)
if((pid = fork())==0) //循环创建进程
break;
if(i<5){
int l,r;
srand(time(NULL));
if(i == 4)
l=0,r=4;//左右解锁编号
else
l=i,r=i+1;
while(1){
sem_wait(&s[l]);//给第l信号量加锁
if(sem_trywait(&s[r]) == 0){//尝试对第r信号加锁
printf("%c is eating \n",'A'+i);
sem_post(&s[r]);//对第r信号解锁
}
sem_post(&s[l]);//对第l信号解锁
sleep(rand()%5);
}
exit(0);
}
//回收子进程
for(i=0;i<5;i++)
wait(NULL);
//销毁信号量
for(i=0;i<5;i++)
sem_destroy(&s[i]);
//
munmap(s,sizeof(sem_t)*5);
return 0;
}