线程共享空间验证
Linux线程与进程相比,最大的区别之一就是内存空间的不同。当我们创建一个进程时,系统会专门创建一个内存空间。而进程会共享内存空间,这也是进程效率比较高的原因。
我们在此建立两个线程,对同一个变量进行操作。看是否能改变变量的值。
代码如下:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
int a = 10;//两个线程操作a变量
void *fun1()
{
while(1){
printf("a:%d\n",++a);
sleep(1);}
}
void *fun2()
{
while(1){
printf("a:%d\n",++a);
sleep(1);}
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,fun1,NULL);//创建线程1
pthread_create(&t2,NULL,fun2,NULL);//创建线程2
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
结果如下:
可以看到两个线程同时操作a变量
我接下来,们在两个线程中建立局部变量。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
void *fun1()
{
int a=10;
while(1){
printf("a1:%d\n",++a);
sleep(1);}
}
void *fun2()
{
int a;
a=10;
while(1){
printf("a2:%d\n",++a);
sleep(1);}
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,fun1,NULL);
pthread_create(&t2,NULL,fun2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
结果如下:
可以看到虽然是一样的变量名,但有自己的空间,并不会互相影响。
这是线程独立申请了堆栈空间
线程锁同步互斥及加锁解锁
互斥锁概述
Linux中提供一把互斥锁mutex(也称之为互斥量)。
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。
所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。
因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。
函数原型:
```c
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参1:传出参数,调用时应传 &mutex
参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。
<2>、加锁。可理解为将mutex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
<3>、尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
<4>、解锁。可理解为将mutex ++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
<5>、销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3、加锁与解锁
lock与unlock:
lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。
可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++
lock与trylock:
lock加锁失败会阻塞,等待锁释放。
trylock加锁失败直接返回错误号(如:EBUSY),不阻塞
<2>、加锁。可理解为将mutex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
<3>、尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
<4>、解锁。可理解为将mutex ++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
<5>、销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3、加锁与解锁
lock与unlock:
lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。
可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++
lock与trylock:
lock加锁失败会阻塞,等待锁释放
trylock加锁失败直接返回错误号(如:EBUSY),不阻塞
互斥锁应用场景
未加锁代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
void *fun1()
{
while(1){
printf("1hello");
printf("1world\n");
sleep(1);
}
}
void *fun2()
{
while(1){
printf("2hello");
printf("2world\n");
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,fun1,NULL);
pthread_create(&t2,NULL,fun2,NULL);
while(1){
printf("0hello");
printf("0world\n");
sleep(1);}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
1hello 与 1world配对
其它同理,我们现在用三个线程运行这个代码:
结果和我们想的并不一样,结果并不能配对。我们使用互斥锁就可以解决这种无法同步的问题
加锁代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
pthread_mutex_t mutex1;//锁为全局变量
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//int pthread_mutex_lock(pthread_mutex_t *mutex);
//int pthread_mutex_trylock(pthread_mutex_t *mutex);
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
//int pthread_mutex_destroy(pthread_mutex_t *mutex);
void *fun1()
{
while(1){
pthread_mutex_lock(&mutex1);//上锁
printf("1hello");
printf("1world\n");
pthread_mutex_unlock(&mutex1);//解锁
sleep(1);//让出cpu时间片
}
}
void *fun2()
{
while(1){
pthread_mutex_lock(&mutex1);
printf("2hello");
printf("2world\n");
pthread_mutex_unlock(&mutex1);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,fun1,NULL);
pthread_create(&t2,NULL,fun2,NULL);
pthread_mutex_init(&mutex1,NULL);
while(1){
pthread_mutex_lock(&mutex1);
printf("0hello");
printf("0world\n");
pthread_mutex_unlock(&mutex1);
sleep(1);}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex1);//销毁锁
}
运行效果如下:
效果非常稳定
互斥锁对临界资源的保护
临界资源是每个线程所争夺的资源,全局变量或者static静态变量较多。我们需要限制线程的访问,否则资源会错乱。
我们现在模拟一个抢票的应用,来了解互斥锁对临界资源的控制
票数就是临界资源
未加锁代码:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//int pthread_mutex_lock(pthread_mutex_t *mutex);
//int pthread_mutex_trylock(pthread_mutex_t *mutex);
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
//int pthread_mutex_destroy(pthread_mutex_t *mutex);
int ticket=1000;
pthread_mutex_t mutex;
void *fun1(void *pro)
{
int id;
id = *((int *)pro);
while(1){
if(ticket>0){
ticket--;
printf("I AM %d pthread,I get %d ticket\n",id,ticket);
}else{
printf("ticket are sold\n");
break;
}
}
}
int main()
{
int i;
pthread_t tid[5];
pthread_mutex_init(&mutex,NULL);
for(i=0;i<5;i++){
pthread_create(&tid[i],NULL,fun1,(void *)&i);
}
for(i=0;i<5;i++){
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mutex);
}
~
效果如下:
可以看到票数完后,还有线程在抢票。这是因为线程取有三个阶段,从内存读取数据到cpu寄存器,cpu执行运算,将数据返回内存。在一线程某个阶段可能被二线程打断,导致数据错误。所以加锁是非常重要的
加锁后
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//功能:创建一个新的线程 原型
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
//参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数,若参数过多,可用结构体传入
// 返回值:成功返回0;失败返回错误码
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//int pthread_mutex_lock(pthread_mutex_t *mutex);
//int pthread_mutex_trylock(pthread_mutex_t *mutex);
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
//int pthread_mutex_destroy(pthread_mutex_t *mutex);
int ticket=100;
pthread_mutex_t mutex;
void *fun1(void *pro)
{
int id;
id = *((int *)pro);
while(1){
pthread_mutex_lock(&mutex);
if(ticket>0){
printf("I AM %d pthread,I get %d ticket\n",id,ticket--);
}else if(ticket==0||ticket<0){
printf("ticket are sold out\n");
break;
}
pthread_mutex_unlock(&mutex);
}
}
int main()
{
int i;
pthread_t tid[5];
// pthread_t t1;
// pthread_t t2;
// pthread_t t3;
// pthread_t t4;
// pthread_t t5;
// pthread_create(&t1,NULL,fun1,NULL);
// pthread_create(&t2,NULL,fun1,NULL);
// pthread_create(&t3,NULL,fun1,NULL);
// pthread_create(&t4,NULL,fun1,NULL);
// pthread_create(&t5,NULL,fun1,NULL);
pthread_mutex_init(&mutex,NULL);
for(i=0;i<5;i++){
pthread_create(&tid[i],NULL,fun1,(void *)&i);
}
for(i=0;i<5;i++){
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mutex);
}
效果如下:
死锁
死锁是两个线程之间相互阻塞,无法继续运行下去
线程1先拿A锁,再拿B锁
线程2先拿B锁,再拿A锁
两给线程都无法获得自己想要的锁,互相堵塞
pthread_mutex_t mutex0;
pthread_mutex_t mutex1;
int a=10;
void *fun1()
{
pthread_mutex_lock(&mutex0);
sleep(1);
pthread_mutex_lock(&mutex1);
while(1){
printf("a1:%d\n",++a);}
// pthread_mutex_unlock(&mutex0);
// pthread_mutex_unlock(&mutex1);
}
void *fun2()
{
pthread_mutex_lock(&mutex1);
sleep(1);
pthread_mutex_lock(&mutex0);
while(1){
printf("a2:%d\n",++a);}
// pthread_mutex_unlock(&mutex1);
// pthread_mutex_unlock(&mutex0);
}
两个都互相抢手里握住的锁,导致一直阻塞。