Linux系统编程(线程同步 互斥锁)

news2025/1/19 17:14:26

文章目录

  • 前言
  • 一、什么是线程同步
  • 二、不使用线程同步访问共享资源可能出现的问题
  • 三、互斥锁概念
  • 四、互斥锁使用
    • 1.初始化线程锁的方式
    • 2.使用代码
  • 五、死锁的产生和解决方法
    • 1.什么是死锁
    • 2.为什么会产生死锁
    • 3.怎么解决死锁问题
  • 总结


前言

本篇文章带大家学习线程的同步。

一、什么是线程同步

线程同步是指协调多个线程之间的执行顺序,以确保共享资源的正确访问和数据的一致性。当多个线程同时操作共享数据时,如果没有适当的同步机制,就会出现数据竞争和不一致的情况。

线程同步的目的是为了保证共享资源在多线程环境下的安全访问,避免数据冲突和并发缺陷。通过使用同步机制,可以使得多个线程按照一定的顺序来访问共享资源,避免出现竞态条件(Race Condition)和不确定性的结果。

常用的线程同步机制包括:

1.互斥锁(Mutex):互斥锁是一种常见的同步机制,用于保护共享资源,一次只允许一个线程访问共享资源。当一个线程获取到互斥锁之后,其他线程必须等待该线程释放锁之后才能继续访问。

2.信号量(Semaphore):信号量是一种用于控制并发访问数量的同步机制。通过设置一个计数器,限制同时访问某个资源的线程数量。当计数器大于0时,线程可以获取许可进行访问,访问后计数器减少;当计数器为0时,线程等待其他线程释放许可。

3.条件变量(Condition Variable):条件变量用于线程之间的条件等待与信号通知。线程可以在某个条件成立时等待条件变量,而其他线程在满足条件时发出信号通知等待线程继续执行。

4.栅栏(Barrier):栅栏用于线程的同步和屏障等待。多个线程在某个位置等待,直到所有线程都到达屏障位置,然后才能继续执行后面的操作。

`线程同步的正确使用可以避免数据竞争和不一致性问题,提高并发程序的正确性和可靠性。然而,不正确或过度的同步机制也可能导致性能问题,因此需要根据具体的应用场景进行合理的同步策略和设计。``

二、不使用线程同步访问共享资源可能出现的问题

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


int i = 0;

void* Thread1(void *arg)
{
    while (1)
    {
        i++;
        printf("Thread1 i = %d\n", i);
        sleep(1);//休眠1s
    }
    
}

void* Thread2(void *arg)
{
    while (1)
    {
        i++;
        printf("Thread2 i = %d\n", i);
        sleep(1);//休眠1s
    }
    
}


int main(void)
{
    pthread_t tid1;//线程ID
    pthread_t tid2;//线程ID

    int err = -1;

    err = pthread_create(&tid1, NULL, Thread1, NULL);

    if(err < 0)
    {
        printf("create thread1 is err\n");
    }

    err = pthread_create(&tid2, NULL, Thread2, NULL);

    if(err < 0)
    {
        printf("create thread2 is err\n");
    }

    while (1)
    {
        sleep(1);//休眠1s
    }
    
    

    return 0;
}

运行结果:

由运行结果可以看出不使用线程同步访问共享资源的话可能导致数据的不一致性。
在这里插入图片描述

三、互斥锁概念

互斥锁(Mutex Lock)是一种并发编程中的同步机制,用于保护共享资源的访问,确保在任何给定时间只有一个线程可以执行受保护的代码段。

互斥锁是二进制锁,它有两个状态:锁定(locked)和解锁(unlocked)。当一个线程获取到互斥锁时,它将锁定状态设置为锁定,其他线程尝试获取锁时将被阻塞,直到锁被释放。

互斥锁主要用于解决并发环境下的竞争条件,例如多个线程试图同时访问和修改同一共享资源的情况。通过使用互斥锁,我们可以确保同一时间只有一个线程能够进入临界区(Critical Section),执行对共享资源的访问和操作,避免数据竞争和不一致的结果。

使用互斥锁的基本流程如下:

1.初始化互斥锁:在使用互斥锁前,需要对其进行初始化。

2.获取互斥锁:线程通过调用互斥锁的"锁定"操作,尝试获取互斥锁。如果互斥锁当前处于解锁状态,线程将获取到锁,并将其状态设置为锁定;否则,线程将被阻塞,直到锁被释放。

3.执行临界区代码:一旦线程成功获取到互斥锁,它就可以进入临界区,执行需要保护的代码,访问共享资源。

4.释放互斥锁:线程执行完临界区代码后,应该调用互斥锁的"解锁"操作来释放锁,将互斥锁的状态设置为解锁,以允许其他线程获取锁并访问共享资源。

互斥锁是一种常见的线程同步机制,用于确保共享资源在并发访问时的正确性和一致性。然而,不正确地使用互斥锁可能导致死锁和性能问题,因此,合理的设计和使用是很重要的。

四、互斥锁使用

1.初始化线程锁的方式

静态方法:

静态创建互斥锁意味着在编译时为互斥锁分配静态的内存,并在定义时进行初始化。

在C语言中,可以使用静态初始化宏PTHREAD_MUTEX_INITIALIZER来静态创建并初始化互斥锁。

pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;

动态方法:
动态创建互斥锁意味着在运行时动态分配互斥锁的内存,并使用相关函数进行初始化。

在C语言中,可以使用pthread_mutex_init函数进行动态创建和初始化互斥锁。

示例代码如下:

pthread_mutex_t mutex;

pthread_mutex_init(&mutex, NULL);

// 销毁互斥锁
pthread_mutex_destroy(&mutex);

2.使用代码

示例代码:

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

/*共享变量*/
int val = 0;

pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;

void* Thread1(void* arg)
{
    while (1)
    {
        /*加锁*/
        pthread_mutex_lock(&m_mutex);
        val++;
        /*解锁*/
        pthread_mutex_unlock(&m_mutex);

        printf("thread1 val : %d\n", val);

        sleep(1);
    }
    
}

void* Thread2(void* arg)
{
    while (1)
    {
        /*加锁*/
        pthread_mutex_lock(&m_mutex);
        val++;
        /*解锁*/
        pthread_mutex_unlock(&m_mutex);

        printf("thread2 val : %d\n", val);

        sleep(1);
    }
    
}

int main(void)
{
    pthread_t tid1;
    pthread_t tid2;   

    pthread_create(&tid1, NULL, Thread1, NULL);
    pthread_create(&tid2, NULL, Thread1, NULL);

    
    while(1)
    {
        
        sleep(1);
    }

    

    return 0;
}

五、死锁的产生和解决方法

1.什么是死锁

死锁是指在并发程序中,两个或多个线程或进程因为互相等待对方释放资源而无法继续执行的情况。在死锁中,每个线程都在等待其他线程释放资源,导致所有线程都被阻塞,无法进行下一步操作。

死锁通常发生在多线程或多进程环境中,涉及共享资源的竞争。当多个线程需要访问共享资源时,如果每个线程都持有一个资源并且等待其他线程释放它所需要的资源,就可能发生死锁。

2.为什么会产生死锁

死锁的产生需要满足以下四个必要条件,也被称为死锁的条件:

1.互斥条件(Mutual Exclusion):一个资源一次只能被一个线程或进程占用,即当一个线程或进程访问资源时,其他线程或进程必须等待。

2.请求与保持条件(Hold and Wait):一个线程或进程可以在保持已经获得的资源的同时请求新的资源。

3.不可抢占条件(No Preemption):已经分配给一个线程或进程的资源不能被强制性地抢占,只能在使用完之后自愿释放。

4.循环等待条件(Circular Wait):存在一种等待资源的循环链,即若干个线程或进程之间形成一种头尾相接的循环等待资源的关系。

要解决死锁问题,可以采用死锁预防、死锁避免、死锁检测和死锁恢复等策略。这些策略的目标是打破死锁产生的必要条件,从而避免或解决死锁的发生。

3.怎么解决死锁问题

1.资源有序分配:为了避免循环等待条件,可以对资源进行排序,并确保线程按照相同的顺序请求资源,从而避免产生循环等待。下面是一个示例代码:

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

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void *thread1(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread1: Holding mutex1...\n");

    // 线程1等待一段时间,模拟资源竞争
    sleep(1);

    printf("Thread1: Trying to acquire mutex2...\n");
    pthread_mutex_lock(&mutex2);
    printf("Thread1: Holding mutex2...\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    pthread_exit(NULL);
}

void *thread2(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread2: Holding mutex1...\n");

    // 线程2等待一段时间,模拟资源竞争
    sleep(1);

    printf("Thread2: Trying to acquire mutex2...\n");
    pthread_mutex_lock(&mutex2);
    printf("Thread2: Holding mutex2...\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;

    // 创建两个线程
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("Program completed.\n");

    return 0;
}

在这个示例中,我们确保线程1和线程2都按照相同的顺序访问互斥锁mutex1和mutex2,从而避免了循环等待的情况。

2.避免持有并等待:一种解决死锁问题的方法是要求线程在申请资源时,释放已持有的资源。这样,当线程请求新的资源时,它没有任何资源保持,从而避免了持有并等待的情况。以下是示例代码:

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

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void *thread1(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread1: Holding mutex1...\n");
    pthread_mutex_unlock(&mutex1);

    // 线程1等待一段时间,模拟资源竞争
    sleep(1);

    printf("Thread1: Trying to acquire mutex2...\n");
    pthread_mutex_lock(&mutex2);
    printf("Thread1: Holding mutex2...\n");

    pthread_mutex_unlock(&mutex2);

    pthread_exit(NULL);
}

void *thread2(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread2: Holding mutex1...\n");
    pthread_mutex_unlock(&mutex1);

    // 线程2等待一段时间,模拟资源竞争
    sleep(1);

    printf("Thread2: Trying to acquire mutex2...\n");
    pthread_mutex_lock(&mutex2);
    printf("Thread2: Holding mutex2...\n");

    pthread_mutex_unlock(&mutex2);

    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;

    // 创建两个线程
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("Program completed.\n");

    return 0;
}

在这个示例中,线程1和线程2在获取并释放mutex1之后立即释放它,然后才尝试获取mutex2。这种方法确保了线程在请求新资源之前不保持任何资源,从而避免了持有并等待的情况。

总结

本篇文章主要讲解了线程同步的概念和互斥锁的使用,以及死锁的产生和解决方法。

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

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

相关文章

【JavaSE】String类中常用的字符串方法(超全)

目录 1.求字符串的长度 2.判断字符串是否为空 3.String对象的比较 3.1 判断字符串是否相同 3.2 比较字符串大小 3.3 忽略大小写比较 4.字符串查找 5.转化 5.1 数值和字符串转化 5.1.1 数字转字符串 valueof 5.1.2 valueOf的其他用法 5.1.3 字符串转数字 5.2 大小写转…

innodb存储引擎探究(一)

mysql 体系结构和存储引擎 数据库&#xff1a;物理操作系统文件或者其他形式的文件 实例&#xff1a;mysql数据库由一个共享内存区和后台进程组成 启动mysql实例时&#xff0c;会读取配置文件&#xff0c;安装以下顺序 mysql体系结构 mysql插件式的一个存储引擎可以根据业务…

如何设置 Eclipse Git 插件中的默认作者和提交者

1、git提交代码时默认带出Windows的系统用户 2、Window->Preferences->Team->Configuration->Add Entry 3、输入键值对&#xff08;user.name要设置的值&#xff09;&#xff08;user.email要设置的值&#xff09;->应用保存 4、应用后&#xff0c;再次提交代码…

【AI赋能】人工智能在自动驾驶时代的应用

自我介绍 我是秋说&#xff0c;研究 人工智能、大数据 等前沿技术&#xff0c;传递 Java、Python 等语言知识。 &#x1f4ac;主页链接&#xff1a;秋说的博客 &#x1f4c6; 学习专栏推荐&#xff1a; 人工智能&#xff1a;创新无限&#x1f4ab; MySQL进阶之路&#x1f3c6…

大数据Flink(五十八):Flink on Yarn的三种部署方式介绍

文章目录 Flink on Yarn的三种部署方式介绍 一、​​​​​​​Session模式

Elasticsearch 摄取管道 — 检测到管道的死循环

在数据处理和摄取领域&#xff0c;管道在组织和自动化数据从源到目的地的流动方面发挥着至关重要的作用。 管道是数据按顺序通过的一系列处理阶段&#xff0c;每个阶段负责特定任务。 然而&#xff0c;有时&#xff0c;管道可能会遇到一个重大挑战&#xff0c;称为 “Cycle det…

年薪54840美元|中医学应届博士受聘美国宾大博士后职位

E博士的个人规划是博士毕业即出国做博后工作&#xff0c;当其明确毕业时间后&#xff0c;我们就依照计划提前9个月开始了申请操作。最终落实了宾夕法尼亚大学医学院的博后职位&#xff0c;年薪为54840美元&#xff0c;我们为其实现了毕业即出国的愿望。 E博士背景&#xff1a; …

状态模式(C++)

定义 允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。 应用场景 在软件构建过程中&#xff0c;某些对象的状态如果改变&#xff0c;其行为也会随之&#xff0c;而发生变化&#xff0c;比如文档处于只读状态&#xff0c;其支持的行为和读写…

【Spring】Bean的作用域和生命周期

目录 一、引入案例来探讨Bean的作用域 二、Bean的作用域 2.1、Bean的6种作用域 2.2、设置Bean的作用域 三、Spring的执行流程 四、Bean的声明周期 1、生命周期演示 一、引入案例来探讨Bean的作用域 首先我们创建一个User类&#xff0c;定义一个用户信息&#xff0c;在定义…

线程池监控

如何监控线程池 文章目录 如何监控线程池线程池两个点需要监控第一点:线程的变化情况第二点:任务的变化 用来监控线程变化的方法自定义一个带监控的线程池,然后继承ThreadPoolExecutor,重载构造方法 自定义线程池中线程的名称的4种方式Spring 框架提供的 CustomizableThreadFac…

uniapp 微信小程序 上下滚动的公告通知(只取前3条)

效果图&#xff1a; <template><view class"notice" click"policyInformation"><view class"notice-icon"><image mode"aspectFit" class"img" src"/static/img/megaphone.png"></i…

行业追踪,2023-08-07

自动复盘 2023-08-07 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

0807|IO进程线程day9 IPC对象概念及示例(消息队列、共享内存、信号灯集)

0 什么是IPC机制 概念&#xff1a; IPC机制&#xff1a;Inter Process Communication&#xff0c;即进程间通信机制。 进程与进程间的用户空间相互独立&#xff0c;内核空间共享。所以如果要实现进程间的通信&#xff0c;需要使用进程间通信机制。 分类&#xff08;3类…

资深测试总结,Web自动化测试POM设计模式封装框架,看这篇就够了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 线性脚本 import…

Maven分模块-继承-聚合-私服的高级用法

Maven分模块-继承-聚合-私服的高级用法 JavaWeb知识&#xff0c;介绍Maven的高级用法&#xff01;&#xff01;&#xff01; 文章目录 Maven分模块-继承-聚合-私服的高级用法1. 分模块设计与开发1.1 介绍1.2 实践1.2.1 分析1.2.2 实现 1.3 总结 2. 继承与聚合2.1 继承2.1.1 继承…

服了呀,现在的00后,实在是太卷了

现在的小年轻真的卷得过分了。前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

收藏这8个好用的原型设计工具,轻松制作原型图

在设计工作中&#xff0c;原型设计是非常关键的一步&#xff0c;而原型设计工具又能帮助设计师更轻松地完成设计工作。今天本文将与大家分享8个好用的原型设计工具&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计是一个能在线协作的原型工具&#xff0c;也就是说…

DataGrip 配置 HiveServer2 远程连接访问

文章目录 集群配置 HiveServer2 服务DataGrip 配置 HiveServer2 访问 Hive 集群配置 HiveServer2 服务 1.在 Hive 的配置文件 hive-site.xml 中添加如下参数&#xff1a; <!-- 指定 HiveServer2 运行端口&#xff0c;默认为&#xff1a;10000 --><property><na…

ELK日志分析系统简介

ELK日志分析系统简介 ElasticsearchLogstashKibana主要功能Kibana日志处理步骤ELK的工作原理 日志服务器 提高安全性 集中存放日志 缺陷 ​ 对日志的分析困难 ELK日志分析系统 Elasticsearch 概述:提供了一个分布式多用户能力的全文搜索引擎 核心概念 接近实时 集群 节…

关于安卓jar包修改并且重新发布

背景&#xff1a; 对于某些jar包&#xff0c;其内部是存在bug的&#xff0c;解决的方法无外乎就有以下几种方法&#xff1a; &#xff08;1&#xff09;通过反射&#xff0c;修改其赋值逻辑 &#xff08;2&#xff09;通过继承&#xff0c;重写其方法 &#xff08;3&#xff0…