Linux线程互斥锁——共享空间代码验证,线程互斥锁的应用场景,及对临界资源的保护

文章讲述了Linux线程间的共享空间特性,通过示例展示了线程如何独立申请堆栈空间。接着,介绍了互斥锁的概念,包括加锁、解锁、尝试加锁等操作,以及其在多线程同步中的应用,防止数据混乱。通过抢票模拟展示了互斥锁对临界资源的保护作用,并讨论了死锁问题,即两个线程因争夺资源而相互阻塞的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程共享空间验证

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--(或-1int pthread_mutex_lock(pthread_mutex_t *mutex);

<3>、尝试加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

 <4>、解锁。可理解为将mutex ++(或+1int 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--(或-1int pthread_mutex_lock(pthread_mutex_t *mutex);

<3>、尝试加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

 <4>、解锁。可理解为将mutex ++(或+1int 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);
}

两个都互相抢手里握住的锁,导致一直阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pg_hj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值