一、线程互斥
1.互斥
顾名思义,就是相互排斥,比如你去ATM取钱,你在ATM机里取钱的时候,那别人就不能够再取钱,只能够再外面等待着,取钱这件事就是相互排斥的
2.线程互斥是什么?
某一资源同一时刻只能让一个线程使用,假设有多个线程都要访问同一资源,线程互斥就是同一时刻该资源只能被一个线程访问,而其他线程就只能等待
3.为什么要有线程互斥?
1.多个线程(或者多个进程)之间,执行时的先后顺序不确定,何时执行何时切换出CPU的顺序也不确定;
2.多个线程(或者多个进程)在访问同一变量时的动作往往不是原子的
有时候,变量需要在线程之间进行共享,这样的变量称为共享变量,可以通过数据
之间的共享,来完成线程之间的交互,线程之间是共享虚拟地址空间的,但是有时
候因为共享会带来一些问题
比如:火车票的售卖,假设现在只剩3张票了,但是四个人同一时间都在买票,因
为不互斥,故这四个人都可以买到票,就会使票的数目成为-1,但是在现实生活
中这种情况是允许出现的,买票的四个人就是四个线程,票就是线程之间共享的
同一变量,因为买票操作不互斥,就会出现这种情况,多个线程访问同一变量,
就会出现数据错乱
原子
就是不可拆分的,是CPU调度的最小单位
非原子例子:
int count=0;
++count;
CPU执行命令时,是一条一条的执行的,一定是某条指令要么执行完了,要么执行完了,不会出现执行某条命令执行一半就去执行别的命令,即就是每条命令之间的执行时非原子的,但是在执行某条命令时是原子的,不可被打断的,不可进行拆分
4.线程互斥怎么实现?
5.线程互斥的相关函数
(1)初始化互斥量
静态分配:
pthread_mutex_t mutex=PHREAD_MUTEX_INITIALIZER
动态分配:
int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)
参数说明:
mutex:需要初始化的互斥量(此参数是输出型参数)
输出型参数:就是说这个参数既可以作为函数的参数,也可以作为函数的返回值
attr:创建的互斥量的属性,一般设为NULL
restrict:是C语言中的关键字
功能:告诉此函数的调用者,在进行函数调用时,不允许修改此指针的执行,但是可以修改此指针指向空间的值
ps:推荐使用动态分配初始化互斥量,尽量不要使用静态分配
(2)销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t* mutex);
1.静态分配的PTHREAD_MUTEEX_INITIALZER,不需要销毁,由操作系统进行销毁
2.不要销毁一个已经加锁的互斥量
3.已经销毁的互斥量,要确保后面不会再加锁
(3)互斥量加锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
调用此函数会有两种情况:
1.互斥量处于未锁状态,该函数会将此互斥量锁定,同时返回成功
2.此函数要锁定的互斥量已经被其他线程加锁,那此函数就会一直阻塞等待着,直到此互斥量解锁,此函数才会开始执行;
(4)互斥量解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
注意:加锁和解锁的操作是原子的
6.示例
(1)不加锁
代码:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int count=10;
void*ThreadEnter(void*arg)
{
char*ID=(char*)arg;
while(1)
{
if(count>0)
{
sleep(1);
printf("count=%d,ID=%s\n",count,ID);
count--;
}
else
{
break;
}
}
}
int main()
{
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,ThreadEnter,"thread1");
pthread_create(&tid2,NULL,ThreadEnter,"thread2");
pthread_create(&tid3,NULL,ThreadEnter,"thread3");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
return 0;
}
运行结果:
由结果看:本应该是count=0就退出循环,可是count却减到-1了,导致了数据的错乱
(2)加锁
代码:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int count=10;
pthread_mutex_t mutex;//声明互斥量
void*ThreadEnter(void*arg)
{
char*ID=(char*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
if(count>0)
{
sleep(1);
printf("count=%d,ID=%s\n",count,ID);
count--;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main()
{
pthread_t tid1,tid2,tid3;
pthread_mutex_init(&mutex,NULL);//初始化互斥量
pthread_create(&tid1,NULL,ThreadEnter,"thread1");
pthread_create(&tid2,NULL,ThreadEnter,"thread2");
pthread_create(&tid3,NULL,ThreadEnter,"thread3");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_mutex_destroy(&mutex);//销毁互斥量
return 0;
}
运行结果:
加锁之后,临界区的代码在同一时刻只能由一个线程执行,其他线程只能等待
二、线程同步
1.为什么要使用条件变量
假设我们在ATM里取钱,但是突然发现ATM中没钱了,现在只能在外面等着,因为有可能会有人给里面存钱哦,有两种等待场景:
第一种:一直在外面等着,但是在等待期间,你会时不时的进ATM中查看,机子里有没有钱
第二种:你可以在外面等待的人中找个人,如果ATM中有钱了,让她给你说一下,在此期间,你可以做你自己的事
从这两种情况来看,我们当然首选第二种,因为效率高啊,但是第二种情况的实现需要一些条件,那就是有人给你说,有钱了,如果没有人给你说的话,你就只能自己在哪里等着了
2.条件变量的相关函数
(1)初始化函数
int pthread_cond_init(pthread_cond_t*restrict cond, const pthread_condattr_t* restrict attr)
参数说明:
cond:需要初始化的条件变量
attr:初始化的条件变量的属性,一般设为NULL
(2)销毁条件变量
int pthread_cond_destroy(pthread_cond_t* cond);
(3)等待条件满足
int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutex_t *restrict mutex);
参数说明:
cond:要在这个条件变量上等待
mutex:
(4)唤醒等待
广播式的唤醒等待:
int pthread_cond_broadcast(pthread_cond_t* cond);
此等待可能会给操作系统带来危害,如果有很多的线程在等待,如果广播式的唤醒,所有的线程都想执行自己的代码,就会造成堵塞
一对一式的唤醒等待
int pthread_cond_signal(pthread_cond_t *cond);
3.示例
1.
(1)不加条件变量
代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void*ThreadEnter1(void*arg)
{
(void)arg;
while(1)
{
printf("投篮\n");
sleep(1);
}
}
void*ThreadEnter2(void*arg)
{
(void)arg;
while(1)
{
printf("传球\n");
sleep(3);
}
}
int main()
{
pthread_t t1,t2;
pthread_create(&t1,NULL,ThreadEnter1,NULL);
pthread_create(&t2,NULL,ThreadEnter2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
运行结果:
结果解析:本来应该是一次传球,一次投篮,但是现在结果却是乱序的
(2)加了条件变量
代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void*ThreadEnter1(void*arg)
{
(void)arg;
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("投篮\n");
sleep(1);
}
}
void*ThreadEnter2(void*arg)
{
(void)arg;
while(1)
{
printf("传球\n");
sleep(3);
pthread_cond_signal(&cond);
}
}
int main()
{
pthread_t t1,t2;
pthread_create(&t1,NULL,ThreadEnter1,NULL);
pthread_create(&t2,NULL,ThreadEnter2,NULL);
pthread_cond_init(&cond,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_cond_destroy(&cond);
return 0;
}
运行结果:
结果解析:
第一次的传球是因为先执行了一次传球,然后此线程等待3秒钟,在这3秒钟中,此线程又被操作系统调度了一次,所以又输出了一次传球,从此之后,传球和投篮就同步了