线程安全:
在多个执行流当中对同一个临界资源进行操作访问,而不会造成数据二义。
通过同步与互斥实现线程安全
- 互斥:通过保证同一时间只有一个执行流就可以对临界资源进行访问,一个执行流访问期间,而其他执行流不能访问,来保证数据访问的安全性;通过互斥锁实现。
- 同步:通过一些条件判断来实现执行流对临界资源访问的合理性,有临界资源则访问,,没有则等待,等有了资源再被唤醒;通过条件变量,POSIX标准的信号量实现。
1、互斥锁:
有一个计数器,0(不可访问),1(可访问),初值为1;
每一个线程在访问临界资源之前,先判断计数器的值和当前临界资源的状态(是否有人正在访问);
若可以访问,就将计数器置为0,然后去访问资源;
其他线程在访问这个临界资源的时候,发现不可访问,就陷入等待(将PCB置为可中断休眠状态,则不被CPU所调度);
这个线程访问完毕后,将计数器置为1,允许别的线程访问,并唤醒一个线程(将PCB置为运行态,CPU开始调度)。
#include<pthread.h>
pthread_mutex_t ---互斥锁变量类型(一个结构体)
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//动态初始换互斥锁(较常用)
//restrict mutex---互斥锁变量的地址
//restrict attr---互斥锁的属性(通常不设置,置NULL)
//返回值---成功返回 0;失败返回非 0值(errno)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//静态初始化互斥锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
//在临界资源访问之前加锁,阻塞加锁(若临界资源已经加锁,则阻塞,直至临界资源可以加锁为止)
int pthread_mutex_trylock(pthread_mutex_t* mutex);
//非阻塞加锁(若临界资源已经加锁,直接退出)
int pthread_mutex_timelock(pthread_mutex_t* mutex);
//限时阻塞加锁(若临界资源的已经加锁,则阻塞某个时间段,还不能加锁就退出)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//在临界资源访问之后解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//销毁互斥锁
利用上面接口简单模拟实现一个黄牛抢票的demo:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int tickets = 100;//共有100张票
void* thread_grabbing(void* mutex)
{
while(1)
{
pthread_mutex_lock((pthread_mutex_t*) mutex);//阻塞加锁,
//pthread_mutex_trylock((pthread_mutex_t*)mutex);//非阻塞加锁
//pthread_mutex_timelock((pthread_mutex_t*)mutex);限时等待阻塞加锁
if(tickets > 0)
{
printf("scalper:%p- get a tickets:%d\n",pthread_self(),tickets);//抢到一张票
tickets--;
pthread_mutex_unlock((pthread_mutex_t*) mutex);//抢完一张票解锁,下次和其他黄牛再次竞争抢票
usleep(1);
//为了保持抢票的公平性,让刚抢完票的这个黄牛稍微停一下
}
else//票没了就退出
{
printf("scalper:%p exit\n",pthread_self());
pthread_mutex_unlock((pthread_mutex_t*) mutex);//在任何线程可能退出的地方都要解锁
pthread_exit(NULL);
}
}
return NULL;
}
int main()
{
int i,ret;
pthread_t scalper[4];//四个线程代表四个黄牛
pthread_mutex_t mutex;//定义互斥锁
pthread_mutex_init(&mutex,NULL);
for(i = 0;i < 4;i++)
{
ret = pthread_create(&(scalper[i]),NULL,thread_grabbing,&mutex);//创建四个黄牛线程
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
for(i = 0;i < 4;i++)
{
pthread_join(scalper[i],NULL);//等待线程退出,释放资源;
}
pthread_mutex_destroy(&mutex);//销毁互斥锁
return 0;
}
2、条件变量:
向用户提供两个接口,使一个线程等待的接口和唤醒一个线程的接口 +等待队列;条件变量只是向用户提供了等待与唤醒的功能,但是什么时候等待,什么时候唤醒,需要用户自己来做判断
#include <pthread.h>
pthread_cond_t cond;条件变量类型(一个结构体)
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//动态初始化条件变量(较常用)
//restrict cond---条件变量的地址
//restrict attr---条件的属性(通常不设置,置NULL)
//返回值---成功返回 0;失败返回非 0值(errno)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//静态初始化条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//使当前调用的执行流陷入等待
//restrict cond---条件变量的地址
//restrict mutex---互斥锁变量的地址
int pthread_cond_broadcast(pthread_cond_t *cond);
//广播唤醒所有等待的执行流
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒至少一个条件变量等待队列中的执行流
int pthread_cond_destroy(pthread_cond_t *cond);
//销毁条件变量