目录
1、线程的互斥
2、可重入VS线程安全
3、线程的同步
1)线程互斥的相关概念
2)通过订票示例引入互斥量
#include
#include
#include
int tickets = 1000;//票数,每订购一张票数减1
void* ticket(void* arg)
{
//模拟订票过程,多个线程(执行流)访问该程序
while(1)
{
if(tickets > 0)
{
usleep(500);
printf("%s,抢票成功,剩余票数:%d\n",(char*)arg,--tickets);
}
else
break;
} }
int main()
{
//创建多个线程
pthread_t tid[5];
pthread_create(&tid[0],NULL,ticket,"new thread1");
pthread_create(&tid[1],NULL,ticket,"new thread2");
pthread_create(&tid[2],NULL,ticket,"new thread3");
pthread_create(&tid[3],NULL,ticket,"new thread4");
pthread_create(&tid[4],NULL,ticket,"new thread5");
//线程等待
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
pthread_join(tid[3],NULL);
pthread_join(tid[4],NULL);
return 0;
}
运行结果分析:
第一个原因:当一个执行流执行到if判断为真以后,首先会usleep(500),在这期间ticket还没有进行--操作,因此ticket还是大于0的,如果这时再有其他执行流执行if还是判断为真,然后等待一段时间多个执行流对ticket(此时值为0)进行--就变成了负值。
第二个原因:ticket--本身就不是原子性的,汇编代码如下:
如何解决这个问题?
满足这些条件的实际上就是一把锁,Linux中提供的锁叫做互斥量。注意:要保证加锁后其他线程不能进入临界区,则其他线程必须能够看到锁,也就是说锁也是临界资源(多个线程都可以访问),因此锁在保证临界区前必须先保证自己的安全,也就是说加锁的过程必须是原子性的(保证了多个线程进入该临界区时只允许一个进入)。
3)互斥量的相关接口
初始化互斥量
方法1:静态分配(全局变量)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ;
方法2:动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数:mutex需要初始化的互斥量 attr暂不关注,置NULL即可
返回值:成功返回0,失败返回错误码
注意:如果使用方法1初始化的互斥量不需要销毁
销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *restrict mutex);
注意:不要销毁一个已经加锁的互斥量;已经销毁的互斥量,要确保后面不会在尝试进行加锁。
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
使用互斥量改进售票系统代码
#include
#include
#include
pthread_mutex_t mutex;//初始化互斥量
int tickets = 10000;//票数,每订购一张票数减1
void* ticket(void* arg)
{
//模拟订票过程,多个线程(执行流)访问该程序
while(1)
{
pthread_mutex_lock(&mutex);//加锁
if(tickets > 0)
{
printf("%s,抢票成功,剩余票数:%d\n",(char*)arg,--tickets);
pthread_mutex_unlock(&mutex);//解锁
usleep(500);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main()
{
//创建多个线程
pthread_t tid[5];
pthread_create(&tid[0],NULL,ticket,"new thread1");
pthread_create(&tid[1],NULL,ticket,"new thread2");
pthread_create(&tid[2],NULL,ticket,"new thread3");
pthread_create(&tid[3],NULL,ticket,"new thread4");
pthread_create(&tid[4],NULL,ticket,"new thread5");
//线程等待
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
pthread_join(tid[3],NULL);
pthread_join(tid[4],NULL);
//销毁互斥量
pthread_mutex_destroy(&mutex);
return 0;
}
4)互斥量原理
语言层面单纯的i++/++i等并不是具有原子性,可能会有数据一致性问题。为了实现互斥锁操作,大多数体系结构都实现了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相互交换,由于只有一跳指令,保证了原子性。
1)相关概念
2)常见线程不安全的情况
不保护共享变量的函数
函数状态随着被调用,状态发生变化的函数
返回指向静态变量指针的函数
调用线程不安全函数的函数
3)常见的线程安全的情况
每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
类或者接口对于线程来说都是原子操作
多个线程之间的切换不会导致该接口的执行结果存在二义性
4)常见不可重入的情况
调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
可重入函数体内使用了静态的数据结构
5)常见可重入的情况
不使用全局变量或静态变量
不使用用malloc或者new开辟出的空间
不调用不可重入函数
不返回静态或全局数据,所有数据都有函数的调用者提供
6)可重入与线程安全的区别与联系
函数是可重入的,那就是线程安全的
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
可重入函数是线程安全函数的一种
线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
1)常见锁的概念
死锁是指在在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程占用的不会释放的资源而处于一种永久等待的状态。
死锁的四个必要条件
死锁的避免
死锁的避免算法
3)Linux线程同步相关概念
4)什么是线程同步?为什么要存在同步?
在保证线程安全的条件下,让多个执行流按照特定的顺序访问临界资源,我们称之为同步。
线程同步保证了多个线程协同完成任务的安全性和高效性。
5)线程同步相关接口
初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:cond要初始化的条件变量 attr:暂不关注,置空即可
返回值:成功返回0,失败返回错误码
销毁条件变量
int pthread_cond_desroy(pthread_cond_t *restrict cond);//销毁条件变量
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:cond等待的条件变量 mutex:互斥量
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
#include
#include
#include
pthread_mutex_t mutex;
pthread_cond_t cond;
void* fun1(void* arg)
{
while(1)
{
sleep(1);//每隔一秒发送一个信号
pthread_cond_signal(&cond);
}
}
void* fun2(void* arg)
{
while(1)
{
//等待,当接收到信号就会执行下面输出语句。
pthread_cond_wait(&cond,&mutex);
printf("开始行动\n");
}
}
int main()
{
//初始化互斥量和条件变量
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
//创建两个线程,一个发送信号,另一个接收到信号打印“活动”
pthread_t t1,t2;
pthread_create(&t1,NULL,fun1,NULL);
pthread_create(&t2,NULL,fun2,NULL);
//线程等待
pthread_join(t1,NULL);
pthread_join(t2,NULL);
//销毁互斥量和条件变量
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
6)为什么pthread_cond_wait需要互斥量