线程安全
在多个执行流中对同一个临界资源进行操作访问,而不会造成数据二义,这就是线程安全。
如何实现线程安全: 同步于互斥
互斥:通过保证同一时间只有一个执行流可以对临界资源进行访问(一个执行流访问期间,其它执行流不能访问),来保证对临界资源访问的安全性。
同步:通过一些条件判断来实现多个执行流对临界资源访问的合理性(有资源则访问,没有资源则等待,等有了资源再被唤醒)
如何实现互斥: 互斥锁
如何实现同步: 条件变量 posix 标准的信号量。
互斥锁:
互斥锁—用于标记当前临界资源的访问状态。
计数器: 0/1 0-不可访问 1-可以访问 计数器初值为1.
将需要等待的pcb状态置为可中断休眠状态,等到解锁后,将这些pcb状态再置位运行态。
每一个线程访问临界资源之前,先判断计数,当前临界资源的状态( 是否有人正在访问---- 正在访问的线程将状态置位了0 --不可访问)
1.第一个线程访问的时候,判断可以访问,因此将状态置为不可访问,然后去访问资源。
2.其它线程访问的时候,发现不可访问,就陷入等待。
3.第一个线程访问临界资源完毕后,将状态置位1-可以访问,唤醒等待的线程,大家重新开始竞争这个资源
等待: 将pcb状态置为可中断休眠(则不被操作系统调度)
**唤醒:**将pcb状态置为运行态(则可以开始调度);
pthread_mutex_t 互斥锁变量类型
pthread_mutex_init(pthread_mutex_t * mutex,pthread_mutexattr_t*attr); 初始化互斥锁
pthread_mutex_lock(pthread_mutex_t *mutex); 在临界资源访问之前加锁
pthread_mutex_unlock(pthread_mutex_t *mutex); 在临界资源访问完毕后解锁
pthread_mutex_destroy(pthread_mutex_t * mutex); 销毁互斥锁
//
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int tickets=100;
pthread_mutex_t mutex;
void *thr_tout(void * arg){
while(1){
//pthread_mutex_lock 阻塞加锁
//pthread_mutex_trylock 非阻塞加锁
//pthread_mutex_timedlock 限时等待的阻塞加锁
//加锁一定是在访问临界资源之前加锁
pthread_mutex_lock(&mutex);
if(tickets>0){
usleep(20000);
tickets--;
//解锁一定是访问临界资源完毕自后
pthread_mutex_unlock(&mutex);
}
else{
//加锁之后在任意有可能退出线程的地方都要解锁
pthread_mutex_unlock(&mutex);
printf("tout:%p exit\n",pthread_self());
pthread_exit(NULL);
}
}
return NULL;
}
int main(){
int i;
int ret;
//互斥锁的初始化
pthread_mutex_init(&mutex,NULL);
pthread_t tid[4];
for(i=0;i<4;i++){
ret=pthread_create(&tid[i],NULL,thr_tout,NULL);
if(ret!=0){
printf("thread create error\n");
return -1;
}
}
for(i=0;i<4;i++){
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mutex);
return 0;
}
同步的实现
保证临界资源访问的合理性,有资源的时候可以获取,没有资源的时候则需要线程等待,等待被唤醒(其他线程产生了一个资源的时候);
条件变量: 向用户提供了两个接口:使一个线程等待的接口和唤醒一个线程的接口+等待队列
条件变量只是提供了等待与唤醒的功能,但是什么时候等待,什么时候唤醒,需要用户自己来判断
- 判断当前是否有没有资源,若没有资源则阻塞执行流。
- 生产了一个资源,唤醒还在等待的执行流
条件变量可以通过提供等待队列和唤醒接口来实现线程间的同步,
但是需要注意的是: 进行条件判断,什么时候该等待中的这个条件判断,需要自己完成。
用户自己判断什么时候该等待,则调用等待接口;另一个执行流促使这个访问条件瞒足之后,需要用户调用唤醒接口来唤醒等待的执行流。
操作接口:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_cond_init(pthread_cond_t * ,pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t * )
使当前调用执行流陷入阻塞等待
里面执行的操作是解锁 -> 休眠 -》 被唤醒后重新加锁 其中解锁和休眠是原子操作
int pthread_cond_signal(pthread_cond_t * );
唤醒至少一个条件变量等待队列中的执行流。
int pthread_cond_broadcast(pthread_cond_t *)
广播唤醒所有等待的执行流。
int pthread_cond_destroy(pthread_cond_t *);
销毁一个条件变量。
下面来一个实例
//这个demo体会条件变量的基本使用以及操作基本接口
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond_gourmet;
pthread_cond_t cond_cheif;
int have_food=0; //0表示没有美食
void * gourmet(void * arg){
while(1){
pthread_mutex_lock(&mutex);
//这里应该用的是循环
//先加锁。
while(have_food==0){
//没有美食,需要等待。
//并且这个解锁操作和陷入休眠的操作必须是原子操作
//解锁 -》 休眠- -》 被唤醒后重新加锁
pthread_cond_wait(&cond_gourmet,&mutex);
}
printf("really delicious\n");
have_food--;
pthread_mutex_unlock(&mutex);
//唤醒厨师
pthread_cond_signal(&cond_cheif);
}
return NULL;
}
void * cheif(void*arg){
while(1){
pthread_mutex_lock(&mutex);
while(have_food==1){
//若做好了美食,但是没人吃也陷入等待。
pthread_cond_wait(&cond_cheif,&mutex);
}
printf("i make a food\n");
have_food++;
//做出美食之后,应该唤醒等待的人,
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond_gourmet);
//唤醒一个执行流
} return NULL;
}
int main(){
pthread_cond_init(&cond_gourmet,NULL);
pthread_cond_init(&cond_cheif,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_t gourmet_tid,cheif_tid;
int ret;
int i;
for(i=0;i<4;i++){
ret=pthread_create(&gourmet_tid,NULL,gourmet,NULL);
if(ret!=0){
printf("pthread create error!\n");
return -1;
}
}
for(i=0;i<4;i++){
ret=pthread_create(&cheif_tid,NULL,cheif,NULL);
if(ret!=0){
printf("pthread create error!\n");
return -1;
}
}
pthread_join(gourmet_tid,NULL);
pthread_join(cheif_tid,NULL);
pthread_cond_destroy(&cond_cheif);
pthread_cond_destroy(&cond_gourmet); //销毁条件变量。
pthread_mutex_destroy(&mutex); //销毁互斥锁。
return 0;
}