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

文章目录

  • 前言
  • 一、什么是线程同步
  • 二、不使用线程同步访问共享资源可能出现的问题
  • 三、互斥锁概念
  • 四、互斥锁使用
    • 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;//线程IDpthread_t tid2;//线程IDint 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.hqwc.cn/news/57015.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Laravel 框架资源嵌套.浅嵌套.自定义表单伪造.CSRF 保护 ④

![请添加图片描述](https://img-blog.csdnimg.cn/154d035aa4db42df99f3b01fbf287e46.gif#pic_center)作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; THINK PHP &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&a…

如何使用ONLYOFFICE+ffmpeg来给视频文件打马赛克

如何使用ONLYOFFICEffmpeg来给视频文件打马赛克 我这里之前写过很多关于ONLYOFFICE使用、安装的系列图文&#xff0c;也写过很多关于ffmpeg使用的图文&#xff0c;那么这次继续&#xff0c;把这两个开源软件放在一起&#xff0c;能碰撞出什么火花般的功能来。 这就是给视频文…

401 · 排序矩阵中的从小到大第k个数

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a; 九章算法 - 帮助更多程序员找到好工作&#xff0c;硅谷顶尖IT企业工程师实时在线授课为你传授面试技巧 class Solution { public:/*** param matrix: a matrix of intege…

在windows配置redis的一些错误及解决方案

目录 Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException:用客户端Redis Desktop Manager一样的密码端口&#xff0c;是可以正常连接的&#xff0c;但是运行java程序之后使用接口请求就会报错 Unable to connect to Redis; nested e…

vue-拦截器

第一步 起步 | Axios 中文文档 | Axios 中文网 安装 npm install axios ​ ​​​​​​ ​ ​ 第二步 ​ ​ 所有的请求都叫http协议 ​ ​ ​ ​ ​ 第三步 ​ 导入后即可使用里面的方法 ​ 任何一个东西都可以导出 ​ ​ 只有一个的时候只需要用defau…

机器学习---监督学习和非监督学习

根据训练期间接受的监督数量和监督类型&#xff0c;可以将机器学习分为以下四种类型&#xff1a;监督学习、非监督学习、半监督学习和强化学习。 监督学习 在监督学习中&#xff0c;提供给算法的包含所需解决方案的训练数据&#xff0c;成为标签或标记。 简单地说&#xff0c;…

org.apache.hadoop.hive.ql.exec.DDLTask. show Locks LockManager not specified解决

Error while processing statement: FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. show Locks LockManager not specified解决 当在Hive中执行show locks语句时&#xff0c;出现"LockManager not specified"错误通常是由于…

ZABBIX 6.4的完全安装步骤

此安装文档是我一步一步的验证过的&#xff0c;按步骤来可以顺畅的安成ZABBIX6.4的部署。 Zabbix 主要有以下几个组件组成&#xff1a; Zabbix Server6.4&#xff1a;Zabbix 服务端&#xff0c;是 Zabbix 的核心组件。它负责接收监控数据并触发告警&#xff0c;还负责将监控数…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块7

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

TCP网络服务器设计

最近设计了一个网络服务器程序&#xff0c;对于4C8G的机器配置&#xff0c;TPS可以达到5W。业务处理逻辑是简单的字符串处理。服务器接收请求后对下游进行类似广播的发送。在此分享一下设计方式&#xff0c;如果有改进思路欢迎大家交流分享。 程序运行在CentOS7.9操作系统上&a…

20天突破英语四级高频词汇——第②天

2&#xfeff;0天突破英语四级高频词汇~第2天加油(ง •_•)ง&#x1f4aa; &#x1f433;博主&#xff1a;命运之光 &#x1f308;专栏&#xff1a;英语四级高频词汇速记 &#x1f30c;博主的其他文章&#xff1a;点击进入博主的主页 目录 2&#xfeff;0天突破英语四级高…

Shell脚本学习-循环的控制命令

break continue exit对比&#xff1a; 示例1&#xff1a;break命令跳出整个循环。 [rootabc scripts]# cat break1.sh #!/bin/bashfor((i0;i<5;i)) doif [ $i -eq 3 ]thenbreakfiecho $i done echo "ok"[rootabc scripts]# sh break1.sh 0 1 2 ok可以看到i等于3及…