Linux——线程深度剖析(二),拿下线程安全
Linux——线程深度剖析(二)互斥同步临界资源临界区线程安全如何保证线程安全0.互斥锁1.互斥锁的初始化2.加锁3.解锁4.互斥锁的销毁实例的运用:黄牛抢票互斥是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它 们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。同步散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的 某种先后
Linux——线程深度剖析(二)
互斥与同步
-
互斥是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它 们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。
-
同步是散布在不同进程之间的若干程序片断,它们的运行必须严格按照规定的 某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
临界资源与临界区
-
临界资源:多个线程共享的资源
-
临界区:多个线程访问临界资源的代码段
线程安全
因为进程中的线程共享了进程的虚拟地址空间,因此线程间通信将变得更加简单,但是缺点也随之而来:缺乏数据的访问控制容易造成数据混乱(因为大家都在争抢访问公共资源),导致程序结果出现二义性。
举个例子:
- 在一个进程中有两个线程,线程A,线程B,两个线程同时对一个整形的全局变量a=10进程++操作,预期结果a=12;
- 当线程A拿到CPU之后,对全局a进行++操作,但是该操作是非原子性的,当线程A将10从内存中读取到寄存器中后,时间片到期,此时线程A就被切换出去了,此时a的值在内存中不变。
- 抢占式执行的前提下,假设线程B在这个时候拿到了CPU资源,完成了++操作且未被打断,并将11写回到了内存当中去。
- 这时候,线程A重新拥有资源,根据程序计数器(保存下一条执行的指令),上下文信息(寄存器中的值),恢复现场,此时线程A从寄存器中拿到的值为10,并完成++操作,将11写回内存。
结果与我们的初衷完全不一样,这就是线程完全问题。
如何保证线程安全
- 通过互斥锁,让线程之间互斥
互斥锁
- 在互斥锁内部有一个计数器,名为互斥量。计数器的取值只能为0或为1。
- 当计数器取值为0时,表示当前线程无法获得互斥锁,无法访问临界资源
- 当计数器取值为1时,表示当前线程可以获取互斥锁,可以访问临界资源。
互斥锁中的计数器如何保证原子性?
获取资源的时候:
1.寄存器中的值赋值为0
2.将寄存器中的值与内存的值交换
3.判断寄存器的值,得出结果
在第二步交换的过程中,系统通过xchgb交换操作,该操作是汇编代码,一次性完成,该操作保证了原子性。
1.互斥锁的初始化
int pthread_mutex_init(pthread_mutex_t *_mutex,_const pthread_mutex_mutexattr_t* _mutexattr)
- mutex:传入互斥锁变量的地址,pthread_mutex_init会初始化互斥变量
- attr:属性,一般传递NULL,采用默认属性
- pthread_mutex_init:互斥锁变量的类型
2.加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
- mutex:传入互斥锁变量的地址。
- 如果mutex当中的计数器的值为1,则pthread_mutex_lock接口就返回了,表示说加锁成功,同时计数器当中值会被更改为0,
- 如果mutex当中的计数器的值为0,则pthread_mutex_lock接口就阻塞了,pthread_mutex_lock接口没有返回,阻塞在该函数的内部,直到加锁成功
3.解锁
int pthread_mutex_unlock(pthread_mutex_t*mutex);
- 不管是哪一个加锁接口加锁成功的,都可以使用该接口进行解锁
- 解锁的时候,会将互斥锁变量当中的计数器的值,从0变成1,表示其他线程可以获取互斥锁
4.互斥锁的销毁
pthread_mutex_destroy (pthread_mutex_t*);
实例的运用:黄牛抢票
创建四个线程,每个线程在抢票时候进行加锁,结束后解锁。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREADCOUNT 4
int g_tickets = 100;
pthread_mutex_t lock_;
void* MyStartThread(void* arg)
{
(void)arg;
while(1)
{
pthread_mutex_lock(&lock_);
if(g_tickets > 0)
{
printf("i am thread %p, i have ticket %d\n", pthread_self(), g_tickets);
g_tickets--;
}
else
{
pthread_mutex_unlock(&lock_);
break;
}
pthread_mutex_unlock(&lock_);
}
return NULL;
}
int main()
{
pthread_mutex_init(&lock_, NULL);
pthread_t tid[THREADCOUNT];
for(int i = 0; i < THREADCOUNT; i++)
{
int ret = pthread_create(&tid[i], NULL, MyStartThread, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
}
for(int i = 0; i < THREADCOUNT; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&lock_);
return 0;
}
注意:
- 在任何开始访问临界资源的地方进行加锁
- 在所有可能线程退出的地方进行解锁,否则可能造成死锁
条件变量(PCB等待队列)
它可以使用在资源不满足的情况下处于休眠状态,让出CPU资源,而资源状态满足时唤醒线程
1.初始化
int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr)
- cond : pthread_cond_t :条件变量的类型,传参的时候还是传入条件变量的地址
- attr:条件变量的属性,通常传递NULL,采用默认属性
2.等待:将调用该接口的线程放到PCB等待队列当中
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
- cond:条件变量
- mutex:互斥锁
- pthread_cond_wait会在内部进行解互斥锁,且顺序是:先放入PCB等待队列,再进行解锁
- 接口内部实现的逻辑:1.从PCB等待队列中移除。 2.抢占互斥锁 如果没有抢到互斥锁,那将会阻塞在pthread_cond_wait的内部抢锁逻辑当中
3.唤醒
int pthread_cond_siganl(pthread_cond_t* cond)
- 通知PCB等待队列当中的线程,将其从队列当中出来,唤醒该线程
- 唤醒至少一个PCB等待队列当中的线程
int pthread_cond_broadcast (pthread_cond_t*cond)
- 唤醒PCB等待队列当中全部的线程
4.释放
int pthread_cond_destroy (pthread_cond_t* cond)
结合条件变量实战一下消费者模型:消费者吃面,生产者做面,当碗里没面时,将消费真放进pcb等待队列,做好后有生产者通知其出队,当碗里有面的时,将生产者放入pcb等待队列,当碗里吃完的时候由消费者通知其做面。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define THREADCOUNT 2
int g_bowl = 0;
pthread_mutex_t g_mut;
pthread_cond_t g_cond;
void* EatStart(void* arg)
{
while(1)
{
//eat
pthread_mutex_lock(&g_mut);
while(g_bowl <= 0)
{
pthread_cond_wait(&g_cond, &g_mut);
}
printf("i am %p, i eat %d\n", pthread_self(), g_bowl);
g_bowl--;
pthread_mutex_unlock(&g_mut);
pthread_cond_signal(&g_cond);
}
return NULL;
}
void* MakeStart(void* arg)
{
while(1)
{
//make
pthread_mutex_lock(&g_mut);
while(g_bowl > 0)
{
pthread_cond_wait(&g_cond, &g_mut);
}
g_bowl++;
printf("i am %p, i make %d\n", pthread_self(), g_bowl);
pthread_mutex_unlock(&g_mut);
pthread_cond_signal(&g_cond);
}
return NULL;
}
int main()
{
pthread_mutex_init(&g_mut, NULL);
pthread_cond_init(&g_cond, NULL);
pthread_t consume[THREADCOUNT], product[THREADCOUNT];
for(int i = 0; i < THREADCOUNT; i++)
{
int ret = pthread_create(&consume[i], NULL, EatStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
ret = pthread_create(&product[i], NULL, MakeStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
}
for(int i = 0; i < THREADCOUNT; i++)
{
pthread_join(consume[i], NULL);
pthread_join(product[i], NULL);
}
pthread_mutex_destroy(&g_mut);
pthread_cond_destroy(&g_cond);
return 0;
}
更多推荐
所有评论(0)