Linux下线程的同步和互斥

1. 线程互斥

与互斥相关的背景概念

  • 临界资源:多个执行流共享的资源叫做临界资源。
  • 临界区:每个线程的内部,访问临界资源的代码,叫做临界区。
  • 原子性(两态):不会被任何调度机制打断的操作,意思就是要操作就操作完它,要么就压根不去执行它。

互斥的概念

  • 在任何时刻,保证只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用。

互斥量的引入-----(锁)

下来我们先来看一段代码:
用多线程来模拟抢票系统

#include< stdio.h>
#include< stdlib.h>
#include< unistd.h>
#include< pthread.h>

int ticket=10; //临界资源

void* get_ticket(void *arg)
{
  int* num=(int*)arg;
  while(1){
    if(ticket>0){
      usleep(100);  //等待(让多个线程进入)
      printf("thread%d ,get a ticket,residue:%d\n",*num,ticket-1);
      ticket--;
    }
    else{
      break;
    }
  }
}

int main()
{
  pthread_t tid[4];
  int arr[]={0,1,2,3};
  int i=0;
  for(;i<4;++i)  // 用循环创建线程
  {
    pthread_create(tid+i,NULL,get_ticket,(void*)(arr+i));
  }
  i=0;
  for(;i<4;++i)
  {
    pthread_join(tid[i],NULL);
  }
  return 0;
}

我们来看实验结果:
在这里插入图片描述

为什么无法获得与现实相符的结论呢?

    1. if语句判断成功后,代码可以并发的切换到其它线程
    1. uslee 这个模拟漫长的业务过程中,可能会有多个线程进入该代码段。
    1. ticket-- 这个操作本身不是一个原子操作。

要解决上面的问题,就需要做到:

  • 代码必须要有互斥行为:当有一个线程进入临界区时,不允许其他线程进入
  • 如果有多个线程同时要求执行临界区代码,并且没有代码在临界区执行时,那么只允许一个线程进入
  • 如果线程不在临界区执行时,那么该线程不能阻止其他的线程进入临界区。

终于“千呼万唤始出来” 要做到以上三点,本质上就是需要一把
Linux上提供的这把锁叫互斥量

锁的使用方法

在这里插入图片描述

互斥量的使用(接口)

初始化互斥量

两种方法:

  • 法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
销毁互斥量
int pthread_mutex_destroy(pthread_mutex *mutex);

销毁互斥量要注意:

  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex); 
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
返回值: 成功返回0, 失败返回错误号

调用pthred_mutex_lock 时,还会遇到的情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

改进上面的售票系统:

#include< stdio.h>
#include< stdlib.h>
#include< unistd.h>
#include< pthread.h>

int ticket=10;
pthread_mutex_t lock; //定义一个锁

void* get_ticket(void *arg)
{
  int* num=(int*)arg;
  while(1){
    usleep(100);
      pthread_mutex_lock(&lock);  //访问临界资源前加锁
    if(ticket>0){
      usleep(1000);
      printf("thread%d ,get a ticket,residue:%d\n",*num,ticket-1);
      ticket--;
      pthread_mutex_unlock(&lock);  //访问完毕解锁
    }
    else{
      pthread_mutex_unlock(&lock);
      break;
    }
  }
}

int main()
{
  pthread_t tid[4];
  int arr[]={0,1,2,3},i=0;;
  pthread_mutex_init(&lock,NULL);  // 初始化该锁
  for(;i<4;++i)
  {
    pthread_create(tid+i,NULL,get_ticket,(void*)(arr+i));
  }
  i=0;
  for(;i<4;++i)
  {
    pthread_join(tid[i],NULL);
  }
  pthread_mutex_destroy(&lock);  //销毁
  return 0;
}

结果展示:
在这里插入图片描述

锁的实现原理 (是如何保证原子性的)

  • 为了实现互斥锁的操作,大多数的体系结构都提供了swap或exchange指令,该指令的作用是吧寄存器和内存单元的数据相交换,由于指令只有一条,所以保证了原子性。

下来我们具体来理解一下:
在这里插入图片描述

  • 看完图我们来总结一下:
  • 锁的底层(是如何保证原子性的)
    1. 保证 ”1“ 只有一份。
    1. exchange 一条汇编完成了寄存器与内存数据的交换。

2.线程同步

什么是同步

  • 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源。

为什么需要同步

  • 为了让多线程协同高效的完成某些事情

怎样做到同步(条件变量)

什么是条件变量

条件变量是利用线程之间共享的的全局变量进行同步的一种机制。
条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的

有关使用条件变量的函数

初始化:

  • int pthread_cond_init (pthread_cond_t *restrict cond,const pthread_condatter_t *restrict attr);
  • 参数:cond:要初始化的条件变量; attr :NULL

销毁:

  • int pthread_cond_destroy(pthread_cond_t *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< stdio.h>
#include< unistd.h>
#include< pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

void* routine_1(void*arg)
{
  char*name=(char*)arg;
  while(1){
    pthread_cond_wait(&cond,&mutex);
    printf("%s:i am processA\n",name);
  }
}

void *routine_2 (void*arg)
{
  char*name=(char*)arg;
  while(1){
    pthread_cond_signal(&cond);
    sleep(1);
    printf("%s:i am processB\n",name);
  }
}
int main()
{
  pthread_t t1,t2;
  pthread_cond_init(&cond,NULL);
  pthread_mutex_init(&mutex,NULL);

  pthread_create(&t1,NULL,routine_1,"processA");
  pthread_create(&t2,NULL,routine_2,"processB");

  pthread_join(t1,NULL);
  pthread_join(t2,NULL);

  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);
  return 0;
}

结果展示:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值