Linux___线程互斥与同步_Y—X的博客-程序员秘密

技术标签: 同步  Linux  linux  多线程  

1. 线程互斥

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

1.1 临界资源、临界区、原子性

  • 临界资源被多个执行流同时访问的共享资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么别做,要么做完。

1.2互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

下面写个多个线程操作共享变量来抢票的售票系统代码:
在这里插入图片描述
在这里插入图片描述
1.为什么可能无法获得争取结果?

  1. if 语句判断条件为真以后,代码可以并发的切换到其他线程。
  2. usleep 这个模拟漫长业务的过程中,可能有很多个线程会进入该代码段。
  3. ticket-- 操作本身就不是一个原子操作。

在这里插入图片描述
要解决以上问题,需要做到三点:

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

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量(mutex)。

1.3互斥量的接口

初始化互斥量:
在这里插入图片描述
销毁互斥量:
在这里插入图片描述
销毁互斥量需要注意

  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

互斥量加锁和解锁:

在这里插入图片描述
注意:在特定线程/进程拥有锁的期间,有新的线程来申请锁,pthread_ mutex_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。unlock之后对线程进程唤醒操作。此外,加锁的粒度越小越好

  • 锁的申请是将lock由1变为0
  • 锁的销毁时将lock由0变为1

改进上面的抢票系统:
在这里插入图片描述

在这里插入图片描述

1.4互斥量(锁)实现原理

在这里插入图片描述
每个线程的寄存器是私有的,在修改数据的时候用的不是拷贝而是xchgb交换,将寄存器的值和内存的值互换。这保证了锁的原子性。因为其他线程申请的话,内存的值为0,申请不到锁。lock:0表示被占, 1表示可以被申请

  1. 整个过程为1的mutex只有一份。
  2. exchange一条汇编完成了寄存器和内存数据的交换。

2. 可重入函数&&线程安全

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

2.1 常见的线程不安全的情况

  1. 不保护共享变量的函数
  2. 函数状态随着被调用,状态发生变化的函数
  3. 返回指向静态变量指针的函数
  4. 调用线程不安全函数的函数

3. 死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因为互相申请被其他进程所占用而不会释放的资源,而处于的一种永久等待状态。

3.1 死锁四个必要条件

  1. 互斥条件:一个资源每次只能被一个执行流使用。
  2. 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

3.2 避免死锁的方法

  1. 破坏死锁的四个必要条件
  2. 加锁顺序一致
  3. 避免锁未释放的
  4. 资源一次性分配

4.线程同步

同步概念:在保证数据安全(一般使用加锁方式)的情况下,让线程能够按照某种特定的顺序访问临界资源,就叫做同步

  • 为什么要存在同步
    • 使多线程同步高效的完成某些事情。

同步实现的事情:当有资源的时候,可以直接获取资源,没有资源的时候,线程进行等待,等待另外的线程生产一个资源,当生产完成的时候,通知等待的线程。

4.1条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中,这种情况就需要用到条件变量。

竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。

条件变量的本质:PCB等待队列+两个接口(等待接口+唤醒接口)

4.2条件变量函数

  1. 定义条件变量
pthread_cond_t  条件变量类型
  1. 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
	cond:传入条件变量的地址
	attr:条件变量的属性,一般设置为NULL,采用默认属性		
  1. 销毁(释放动态初始化的条件变量所占用的内存)
int pthread_cond_destroy(pthread_cond_t *cond)
  1. 等待条件满足(将调用该等待接口的执行流放入PCB等待队列当中)
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
	cond:传入条件变量的地址
	restrict mutex:传入互斥锁变量的地址
  1. 唤醒等待(通知PCB等待当中的执行流来访问临界资源)
int pthread_cond_signal(pthread_cond_t *cond);
参数:
	cond:传入条件变量的地址
	//唤醒至少一个PCB等待队列当中的线程

4.3 为什么会有互斥锁?

  • 同步并没有保证互斥,意味着不同的执行流可以在同一时刻去访问临界资源,所以需要条件变量中的互斥锁来保证互斥,各执行流在访问临界资源的时候,只有一个执行流可以访问。
    在这里插入图片描述
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t lock;
pthread_cond_t cond;

void *task_t2(void *arg)
{
    
  const char *name = (char*)arg;
  while(1){
    
    pthread_cond_wait(&cond, &lock);
    printf("get cond : %s 活动...\n", name);
  }
}

void *task_t1(void *arg)
{
    
  const char *name = (char*)arg;
  while(1){
    
    sleep(rand()%3+1);
    pthread_cond_signal(&cond);
    printf("%s signal done...\n", name);
  }
}


int main()
{
    
  pthread_mutex_init(&lock, NULL);
  pthread_cond_init(&cond, NULL);

  pthread_t t1,t2,t3,t4,t5;
  pthread_create(&t1, NULL, task_t1, "thread1");
  pthread_create(&t2, NULL, task_t2, "thread2");
  pthread_create(&t3, NULL, task_t2, "thread3");
  pthread_create(&t4, NULL, task_t2, "thread4");
  pthread_create(&t5, NULL, task_t2, "thread5");

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);
  pthread_join(t5, NULL);

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

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_50843868/article/details/115874565

智能推荐

C#中异常返回详细错误信息设置_c# 全局异常处理修改返回数据_diaya的博客-程序员秘密

在全局处理文件Global.asax中的Application_Start()方法中加上如下一行代码: protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); // 加上下面这一行,异常会返回详细错误信息 GlobalConfiguration.Configuration.Inc...

wcf webHttpBinding Post 大数据量提交 ios c#客户端_weixin_30875157的博客-程序员秘密

一直在为安卓和苹果写服务,刚开始的时候全用的Get请求用url传所有的参数,由于url长度的限制大的数据量无法传到服务器,提交图片什么的就更不用说了,后来用的POST请求,记录一下,做了一个很简单的小例子:c#源代码提供大家下载10Solution4.rarios代码IOSHttpPostDemo.rar开始做吧!1建一个空工程Solution4,添加一个类库WcfSer...

oracle格式转换函数,Oracle函数之单行转换函数_zqk666mkq的博客-程序员秘密

1ASCIISTR格式:ASCIISTR(C)说明:将字符串C转换为ASCII字符串,即将C中的ASCII字符保留不变,但非ASCII字符则以ASCII表示返回举例:SQL&gt;SELECT ASCIISTR('AB?CDE数据库') A FROM DUAL;A---------------------AB?CDE\6570\636E\5E932BIN_TO_NUM格式:BIN_TO_NUM(n...

Ubuntu16.04 上运行 Hadoop2.7.3 自带example wordCount摸索记录__ThisIllusion的博客-程序员秘密

首先最最重要的写在最前面,也是我觉得个人踩得最深的坑,刚接触hadoop的人,缺少的认识:hdfs的理解:它是一个文件系统,跟linux的文件系统是类似的结构,拥有类似的语法,大概就是你在linux上ls查看文件列表,那么hdfs的无非就是hadoop fs -ls。hadoop的输入输出,都是从hdfs读取和写入的,那么比如运行hadoop的word count例子的时候,网上各种大坑教程中

调用wcf 得不到HttpWebResponse.ContentLength的长度_weixin_30500473的博客-程序员秘密

HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(strUrl); wreq.Timeout = _httpTimeout * 1000; wreq.ReadWriteTimeout = _httpTimeout * 1000; w...

openlayers交互编辑图层,进行增删改查并实时入库_openlayers srsname_杨大大28的博客-程序员秘密

如果有在线修改要素并入库的需求,可以考虑该技术方案,前提是需要用geoserve将postgis数据库的数据发布为wfs服务,然后可以用openlayers 在线进行编辑并实时入库的操作如何删除一个元素

随便推点

Spring实现类私有方法测试通用方案_spring 切面 私有方法_明明如月学长的博客-程序员秘密

现实的业务场景中,可能需要对Spring的实现类的私有方法进行测试。场景描述:比如XXXService里有 两个函数a、函数b。而实现类XXXServiceImpl中实现了函数a、函数b,还包含私有方法函数c和函数d。要写一个XXXTestController来调用XXXServiceImpl的函数c。面临几个问题:1、如果注入接口,则无法调用实现类的私有类。2、如果注...

hive int bigint 强类型_Hive常用命令_weixin_39609770的博客-程序员秘密

Hive中定义变量内置命名空间Hive内置命名空间包含了hivevar、hiveconf、system和env。在Hive中写入hivevar变量hive --define/--hivevar key=value显示变量set env:HOMEset hivevar:keyset key给变量赋值set key=valueset hivevar:key=value在sql语句中调用变量cre...

POST调用WCF方法-项目实践_weixin_30335575的博客-程序员秘密

做即时通信项目时,需要与OA系统对接接口,主要目标是实现在OA里进行一项事项,通过调用我们的接口,即时通知过来,并弹出消息框提示一下。我们的即时通信使用的WCF服务进行通信,在客户端调用通信时,用的就是直接添加服务引用的方式,无可厚非,但是OA系统对接了太多项目,不能使用添加服务引用的方式,问题就来了,不得已只能更改我们的接口。因为不熟悉这一块,浪费了不少时间,总结一下过程,方便以后使用。微软官方...

scss2css vscode设置_VSCode下让CSS文件完美支持SCSS或SASS语法方法_胖广鱼的博客-程序员秘密

VSCode下让CSS文件完美支持SCSS或SASS语法方法习惯Webpack + PostCSS后, 通常PostCSS都是直接对CSS文件进行处理, 但是大部分习惯SCSS/SASS/LESS的朋友也许不适应了. 我专门研究了一下, 在Visual Studio Code编辑器下如果配置相关代码和设置达到CSS文件完美编写SCSS的办法, 其他语法类型原理相似, 这里以SCSS为例.开始配置V...

Linux虚拟文件系统剖析: 文件打开、读、写逻辑_xfs_vm_releasepage_badman250的博客-程序员秘密

            Linux虚拟文件系统剖析:文件打开、读、写逻辑[email protected] Linux文件系统剖析:文件打开操作本文主要通过分析linux系统中的文件打开逻辑,来掌握linux虚拟文件系统相关的数据结构、函数等知识点,将之前的各个点的知识串联成一个整体。系统中给所有文件系统不但依赖VFS,而且...

C++中的iostream/iostream.h以及string/cstring/string.h/CString_Zenhobby的博客-程序员秘密

C++的标准类库被修订了两次,有两个标准 C92和C99,这两个库现在都在并行使用,用 .h 包含的是c92 ,不带 .h 的是c99的头文件,对于普通用户来说这两者没有什么区别,区别是在内部函数的具体实现上。旧的C++头文件是官方明确反对使用的,但旧的C头文件则没有(以保持对C的兼容性)。据说从 Visual C++ .NET 2003 开始,移除了旧的 iostream 库。其实编译器制造