【Linux】:线程(二)互斥

news2024/11/16 6:51:36

互斥与同步

  • 一.线程的局部存储
  • 二.线程的分离
  • 三.互斥
    • 1.一些概念
    • 2.上锁
    • 3.锁的原理
    • 4.死锁

一.线程的局部存储

例子

在这里插入图片描述

在这里插入图片描述

可以看到全局变量是所有线程共享的,如果我们想要每个线程都单独访问g_val怎么办呢?其实我们可以在它前面加上__thread修饰。

在这里插入图片描述

在这里插入图片描述

这就相当于把g_val从全局变量去储存到了局部储存里。每个线程可以单独访问自己的g_val。(注意__thread只能定义内置类型)

在这里插入图片描述

二.线程的分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

如果不关心线程的返回值,join是一种负担(因为它会阻塞我们的主线程),这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

在这里插入图片描述

在这里插入图片描述

这个函数在主函数和当前线程里都可以使用。

三.互斥

1.一些概念

临界资源:多线程执行流共享的资源就叫做临界资源 临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

例如,一个抢票系统

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

int ticket=100;
void *route(void *arg)
{
  char *id = (char*)arg;
  while(1) 
  {
    if (ticket>0) 
    {
      usleep(1000);
      printf("%s sells ticket:%d\n", id, ticket);
      ticket--;
    } 
    else 
    {
      break;
    }
  }
} 
int main()
{
  pthread_t t1, t2, t3, t4;
  pthread_create(&t1, NULL, route, (void*)"thread 1");
  pthread_create(&t2, NULL, route, (void*)"thread 2");
  pthread_create(&t3, NULL, route, (void*)"thread 3");
  pthread_create(&t4, NULL, route, (void*)"thread 4");
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);

  return 0;
}

在这里插入图片描述

可以看到已经抢到了负数,很明显是不符合实际的。这是因为在执行打印ticket操作时,操作系统需要从CPU里读取ticket数据,而当一个线程已经打印了ticket=0后,再执行了减减操作,ticket变为了-1,将ticket的值再CPU里更新;这时切换到了另一个线程,而该线程又恰好正要执行打印ticket操作,那么它从CPU里读取了数据,打印出来就为了负数。

要解决以上问题,需要做到三点:

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

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

在这里插入图片描述

2.上锁

创建锁

在这里插入图片描述

调用函数时可能会出现以下情况:

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

加锁

在这里插入图片描述

在这里插入图片描述

修改代码

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

pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;//初始化
int ticket=100;
void *route(void *arg)
{
  char *id =(char*)arg;
  while(1) 
  {
    pthread_mutex_lock(&mutex);//上锁
    if(ticket>0) 
    {
      usleep(1000);
      printf("%s sells ticket:%d\n", id, ticket);
      ticket--;
      pthread_mutex_unlock(&mutex);//解锁
    } 
    else 
    {
      pthread_mutex_unlock(&mutex);//解锁
      break;
    }
  }
} 
int main()
{
  pthread_t t1, t2, t3, t4;
  pthread_create(&t1, NULL, route, (void*)"thread 1");
  pthread_create(&t2, NULL, route, (void*)"thread 2");
  pthread_create(&t3, NULL, route, (void*)"thread 3");
  pthread_create(&t4, NULL, route, (void*)"thread 4");
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);

  pthread_mutex_destroy(&mutex);//销毁

  return 0;
}

在这里插入图片描述

我们发现票数问题得到了解决,但是票全被一个线程抢走了,这是怎么回事呢?其实是由于不同线程对于锁的竞争能力是不同的,这里当线程2释放锁后,马上又去申请了锁,导致锁一直被线程2拿着,出现了线程饥饿问题。我们可以在外面加上sleep函数,让每个线程释放锁后休息一段时间,避免锁一直在某一个线程上。

在这里插入图片描述

在这里插入图片描述

3.锁的原理

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题。

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下。

在这里插入图片描述

movb语句是把al寄存器置零。

xchgb语句就是把al寄存器里的数据交换与内存里的mutex(1)变量进行一次交换(此时mutex就变为了0)。注意mutex是所有线程共享,也就是说其实1只有一份,当第一个进程将mutex里的1交换走后,后面的线程就无法拿到1,也就是上锁了。

4.死锁

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

死锁的必要条件

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

避免死锁

破坏死锁的四个必要条件。
加锁顺序一致。
避免锁未释放的场景。
资源一次性分配。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面向对象实践小结(含面试题)

继承 作用 提高了代码的复用性。让类与类之间产生了关系。有了这个关系&#xff0c;才有了多态的特性。 代码示范 父类代码 public class Parent {public void say() {System.out.println("父类的say方法");} }子类代码&#xff0c;继承父类&#xff0c;也就拥有…

如何确认网站是否有漏洞,如何找出网站存在的漏洞,找到漏洞该如何处理

如何确认网站或者服务器是否有漏洞 判断一个网站是否是存在漏洞的方法&#xff1a; 1.可以借助德迅云安全漏洞扫描功能来检查漏洞。 2.打开德迅云安全首页&#xff0c;点击最上面导航栏中的“安全产品”。 3.滑到“漏洞扫描”&#xff0c;选择“产品价格”服务。 4.选择您需…

【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解

【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解前言DenseNet讲解Dense Block(稠密块)Dense Layer(稠密层)Transition Layer 过渡层DenseNet模型结构 DenseNet Pytorch代码完整代码附加…

DM8/达梦 数据库管理员使用手册详解

1.1DM客户端存放位置 Windows&#xff1a;DM数据库安装目录中tool文件夹和bin文件夹中。 Linux&#xff1a;DM数据库安装目录中tool目录和bin目录中。 1.2DM数据库配置助手 1.2.1Windows创建数据库 打开数据库配置助手dbca 点击创建数据库实例 选择一般用途 浏览选择数据库…

【基于大数据的人肥胖程度预测分析与可控策略】

基于大数据的人肥胖程度预测分析与可控策略 前言数据获取与清洗数据挖掘与分类建模1. K-means聚类2. 层次聚类3. DBSCAN4. 分类建模 数据可视化模型肥胖程度预测分析与可控策略结语 前言 随着现代生活方式的改变&#xff0c;肥胖问题逐渐成为全球性的健康挑战。为了更好地理解…

湖科大计网:计算机网络概述

一、计算机网络的性能指标 一、速率 有时候数据量也认为是以10为底的&#xff0c;看怎么好算。&#xff08;具体吉大考试用什么待商榷&#xff09; 二、带宽 在模拟信号系统中带宽的含义&#xff0c;本课程中用到的地方是&#xff1a;香农定理和奈奎斯特定理公式的应用之中。 …

未用的引脚如何处理?--持续更新中

前言&#xff1a; 随着集成电路规模的越来越大&#xff0c;如今的大规模芯片都集成了很多功能模块&#xff0c;但是在实际的电路设计中我们又不可能把芯片所有的功能模块(或者说接口)全部用上&#xff0c;因此总会有或多或少的管脚会“用不上”&#xff0c;那这些未用的管脚一般…

maven上传jar包到代码仓库

一、前言 一般被引用的包开发都是要求放在nexus仓库中&#xff0c;等到有jar包服务需要引用该包的时候直接从nexus仓库中获取即可&#xff0c;实现了该引用包的公用 二、代码配置 编辑代码中的pom.xml文件配置 vi pom.xml <distributionManagement><repository>&…

网络安全行业大模型调研总结

随着人工智能技术的发展&#xff0c;安全行业大模型SecLLM&#xff08;security Large Language Model&#xff09;应运而生&#xff0c;可应用于代码漏洞挖掘、安全智能问答、多源情报整合、勒索情报挖掘、安全评估、安全事件研判等场景。 本文首先介绍汇总了安全行业的大模型…

Java、JDK、JRE、JVM

Java、JDK、JRE、JVM 一、 Java 广义上看&#xff0c;Kotlin、JRuby等运行于Java虚拟机上的编程语言以及相关的程序都属于Java体系的一员。从传统意义上看&#xff0c;Java社区规定的Java技术体系包括以下几个部分&#xff1a; Java程序设计语言各种硬件平台上的Java虚拟机实…

Linux 进程终止

引入 在写 C 语言程序的时候&#xff0c;我们必写的结构就是&#xff1a; int main() {return 0; }在学习 C 语言的时候&#xff0c;我们好像并没有讨论过这个 return 0 有什么用&#xff0c;是干什么的&#xff01;return 1 可以吗&#xff1f;return 的返回值给谁看&#x…

英伟达危机大爆发!一夜之间,四面楚歌

今年以来&#xff0c;AI大模型明争暗斗、百花齐放。 但不管各种大模型打的有多厉害&#xff0c;很多人都认为“卖铲子”的英伟达才是最大赢家。 看一下英伟达今年的股票就知道英伟达赚的是多么盆满钵满。 英伟达CEO黄仁勋在发布 H200显卡时&#xff0c;应该是今年最意气风发的…

Swagger2的使用

手写Api文档的几个痛点&#xff1a; 文档需要更新的时候&#xff0c;需要再次发送一份给前端&#xff0c;也就是文档更新交流不及时。 接口返回结果不明确 不能直接在线测试接口&#xff0c;通常需要使用工具&#xff0c;比如postman 接口文档太多&#xff0c;不好管理 Sw…

Linux:进程优先级与命令行参数

目录 1.进程优先级 1.1 基本概念 1.2 查看系统进程 1.3 修改进程优先级的命令 2.进程间切换 2.1 相关概念 2.2 Linux2.6内核进程调度队列&#xff08;了解即可&#xff09; 3.命令行参数 1.进程优先级 1.1 基本概念 cpu资源分配的先后顺序&#xff0c;就是指进程的优…

探秘机器学习核心逻辑:梯度下降的迭代过程 (图文详解)

一 需求解函数 f() 和 g()函数分别为求y值和求导数的函数。 目的&#xff1a;求该函数的最小值&#xff1a; 代码&#xff1a; import numpy as np import matplotlib.pyplot as plt f lambda x : (x - 3.5) ** 2 - 4.5 * x 10 g lambda x : 2 * (x - 3.5) - 4.5x np.l…

class064 Dijkstra算法、分层图最短路【算法】

class064 Dijkstra算法、分层图最短路【算法】 算法讲解064【必备】Dijkstra算法、分层图最短路 code1 743. 网络延迟时间 // Dijkstra算法模版&#xff08;Leetcode&#xff09; // 网络延迟时间 // 有 n 个网络节点&#xff0c;标记为 1 到 n // 给你一个列表 times&…

一个或多个筛选器或者Listeners启动失败 的问题

核心&#xff1a; 这个就是有好多情况会导致这个问题&#xff0c;像是文件找不到&#xff0c;缺少jar包等原因&#xff0c;还是要看报错的具体信息。 报错情况&#xff1a; 一个或多个listeners启动失败&#xff0c;更多详细信息查看对应的容器日志文件 由于之前的错误&#x…

Flutter视频播放器在iOS端和Android端都能实现全屏播放

Flutter开发过程中&#xff0c;对于视频播放的三方组件有很多&#xff0c;在Android端适配都挺好&#xff0c;但是在适配iPhone手机的时候&#xff0c;如果设置了UIInterfaceOrientationLandscapeLeft和UIInterfaceOrientationLandscapeRight都为false的情况下&#xff0c;无法…

基于lambda简化设计模式

前言 虽说使用设计模式可以让复杂的业务代码变得清晰且易于维护&#xff0c;但是某些情况下&#xff0c;开发可能会遇到我为了简单的业务逻辑去适配设计模式的情况&#xff0c;本文笔者就以四种常见的设计模式为例&#xff0c;演示如何基于lambda来简化设计模式的实现。 策略…

postgresql自带指令命令系列二

简介 在安装postgresql数据库的时候会需要设置一个关于postgresql数据库的PATH变量 export PATH/home/postgres/pg/bin:$PATH&#xff0c;该变量会指向postgresql安装路径下的bin目录。这个安装目录和我们在进行编译的时候./configure --prefix [指定安装目录] 中的prefix参…