Linux 多线程同步机制(上)

文章目录

  • 前言
  • 一、线程同步
  • 二、互斥量 mutex
  • 三、死锁
  • 总结

前言


一、线程同步

在多线程环境下,多个线程可以并发地执行,访问共享资源(如内存变量、文件、网络连接  等)。
这可能导致 数据不一致性, 死锁, 竞争条件等 问题。
为了解决这些问题,需要使用同步机制来确保线程间的协作和互斥访问共享资源。

“同步” 的目的 是为了避免数据的混乱,解决与时间有关的错误。实际上,不仅线程需要同步,进程间,信号间等等都需要同步机制。

线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时 其他线程为保证数据的一致性,不能调用该功能。

二、互斥量 mutex

互斥锁(Mutex,全称为 Mutual Exclusion)是一种常用的同步机制,用于保护共享资源免受多个线程同时访问和修改的影响。互斥锁提供了一种互斥访问的机制,同一时间只允许一个线程获取锁并访问被保护的资源。

每个线程在对资源操作前都尝试进行先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程也还是竞争的。
但 通过 “锁” 就将资源的访问变成互斥操作,而后与时间有关的错误也就不会再产生了。

1. 互斥锁的基本操作包括两个关键操作:

  • 加锁(Lock):线程通过申请互斥锁来获取对共享资源的访问权。如果互斥锁当前未被其他线程获取,线程成功获得锁然后进入临界区(Critical Section),可以访问共享资源。如果互斥锁已经被其他线程获取,申请锁的线程将被阻塞,直到锁被释放。

  • 解锁(Unlock):线程在完成对共享资源的访问之后,释放互斥锁,使得其他线程可以申请并获取锁。

2. 互斥锁的主要应用函数 :

pthread_mutex_init: 用于初始化互斥锁变量。
pthread_mutex_destroy: 用于销毁互斥锁对象。
pthread_mutex_lock: 用于加锁,如果互斥锁已被其他线程占用,则当前线程阻塞。
pthread_mutex_trylock: 尝试加锁,如果互斥锁已被其他线程占用,则返回一个失败状态而不阻塞线程。
pthread_mutex_unlock: 用于解锁,释放互斥锁使其他线程可以获取。

3. 初始化线程锁 :
有两种方式可以对互斥锁进行初始化:静态初始化和动态初始化。

  • 静态初始化: 是在定义互斥锁变量时直接进行初始化,不需要调用特定的初始化函数。
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化互斥锁变量。
  • 动态初始化:动态初始化是在运行时使用初始化函数对互斥锁进行初始化。
    pthread_mutex_init(&mutex, NULL);

4. 示例代码
在下面代码中,main 函数中有一个主线程 打印小写字母,my_thread 为 子线程 打印 大写字母。两个线程通过互斥锁来访问 共享资源。

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

pthread_mutex_t lock;							// 创建 互斥锁

void *my_thread(void *arg)
{
	srand(time(NULL));							// 设置随机种子
	
	while(1)
	{
		pthread_mutex_lock(&lock);
		printf("ABC ");
		sleep(rand() % 3);
		printf("XYZ\n");

		pthread_mutex_unlock(&lock);
		sleep(rand() % 3);							// 休眠随机秒,释放cpu资源
	}
	
	pthread_exit(NULL);
}


int main(void)
{
	pthread_t tid;
	int ret;

	srand(time(NULL));									// 设置随机种子

	ret = pthread_mutex_init(&lock,  NULL);				// 初始化互斥锁
	if(ret != 0)
	{
		printf("pthread_mutex_init err\n");
	}

	ret = pthread_create(&tid, NULL, my_thread, NULL);
	if(ret != 0)
	{
		printf("pthread_create err\n");
	}

	while(1)
	{
		pthread_mutex_lock(&lock);
		printf("abc ");
		sleep(rand() % 3);
		printf("xyz\n");

		pthread_mutex_unlock(&lock);
		sleep(rand() % 3);
	}

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

	pthread_join(tid,NULL);				  		// 等待回收线程,获取回收状态

	return 0;
}

注意 :
锁粒度(Lock Granularity):锁的粒度应该尽可能小,以避免锁定过长时间,从而降低了并发性能。

三、死锁

死锁产生的原因:死锁是指多个线程或进程因为彼此相互等待对方所持有的资源而无法继续执行的状态。

解决:

  1. 使用资源的有序性:通过规定线程获取资源的顺序,避免出现循环等待的情况。例如,可以约定所有线程按照一定的顺序获取资源,从而避免死锁的发生。

如果下面两个线程 获取资源的顺序是相反的,则可能会产生死锁。可以将 线程 B 先获取 m1锁,再获取 m2锁。
在这里插入图片描述
以下面代码的方式获取锁,不会存在死锁风险。

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

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void *my_thread1(void *arg)
{
	pthread_mutex_lock(&lock1);
	printf("my_thread1 : begin\n");

	pthread_mutex_lock(&lock2);
	printf("my_thread1 : end\n");

	pthread_mutex_unlock(&lock2);
	pthread_mutex_unlock(&lock1);
	
	pthread_exit(NULL);
}

void *my_thread2(void *arg)
{
	pthread_mutex_lock(&lock1);
	printf("my_thread2 : begin\n");

	pthread_mutex_lock(&lock2);
	printf("my_thread2 : end\n");

	pthread_mutex_unlock(&lock2);
	pthread_mutex_unlock(&lock1);
	
	pthread_exit(NULL);
}

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

	ret = pthread_create(&tid1, NULL, my_thread1, NULL);
	if(ret != 0)
	{
		printf("pthread1_create err\n");
	}

	ret = pthread_create(&tid2, NULL, my_thread2, NULL);
	if(ret != 0)
	{
		printf("pthread2_create err\n");
	}

	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);

	return 0;
}
  1. 设置超时机制:在请求资源时,设置一个超时时间,在超过该时间后如果仍未获得资源,则放弃等待,释放已经获取的资源,避免长时间的死锁等待。

总结

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RedisDesktopManager(redis客户端,可输入用户名密码)

RedisDesktopManager&#xff08;redis客户端&#xff0c;可输入用户名密码&#xff09; Redis桌面管理器&#xff08;又名RDM&#xff09; - 是一个用于Windows&#xff0c;Linux和MacOS的快速开源Redis数据库管理应用程序。可以使用url连接或账号密码。 redis设置账号密码后…

【Rust日报】2023-08-27 Collie:专为您打造的简约 RSS 阅读器

第一个 Rust 项目&#xff1a;模板引擎 大家好&#xff01; 我是 Rust 新手&#xff0c;决定为我的第一个 Rust 项目编写一个模板引擎。并不是出于需要&#xff0c;因为生态系统已经有一些不错的选择&#xff0c;而是为了乐趣和学习。 这是我第一次尝试这种类型的库&#xff0c…

Python框架【模板继承、继承模板实战、装饰器、蓝图(介绍、单文件、目录结构、模版文件、静态文件 url_for函数子域名实现)】(五)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

C语言练习题解析:挑战与突破,开启编程新篇章!(1)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言刷题专栏&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐&a…

政府科技项目验收全流程分享

科技验收测试 &#xff08;验收申请→主管部门初审→科技厅审核→组织验收→归档备案→信用管理&#xff09;&#xff1a; &#xff08;一&#xff09;验收申请 项目承担单位通过省科技业务管理系统提交验收申请。 按期完成的项目&#xff0c;项目承担单位应当在项目合同书…

Kotlin的Lambda闭包语法

Lambda 表达式是一种在现代编程语言中常见的特性&#xff0c;它可以用来创建匿名函数或代码块&#xff0c;使得将函数作为参数传递、简化代码以及实现函数式编程范式变得更加便捷。Lambda 表达式在函数式编程语言中得到广泛应用&#xff0c;也在诸如 Java 8 和 Kotlin 等主流编…

设计模式之桥接模式

文章目录 一、介绍二、案例1. 组件抽象化2. 桥梁抽象化 一、介绍 桥接模式&#xff0c;属于结构型设计模式。通过提供抽象与实现之间的桥接结构&#xff0c;把抽象化与实现化解耦&#xff0c;使得二者可以独立变化。 《Head First 设计模式》&#xff1a; 将抽象和实现放在两…

TensorFlow-slim包进行图像数据集分类---具体流程

TensorFlow中slim包的具体用法 1、训练脚本文件&#xff08;该文件包含数据下载打包、模型训练&#xff0c;模型评估流程&#xff09;3、模型训练1、数据集相关模块&#xff1a;2、设置网络模型模块3、数据预处理模块4、定义损失loss5、定义优化器模块 本次使用的TensorFlow版本…

web自动化框架:selenium学习使用操作大全(Python版)

目录 一、浏览器驱动下载二、selenium-python安装&#xff08;打开网站、操作元素&#xff09;三、网页解析&#xff08;HTML、xpath&#xff09;四、selenium基本操作1、元素定位八种方法2、元素动态定位3、iframe切换4、填充表单_填充文本框5、填充表单_单选按钮6、填充表单_…

RT-Thread在STM32硬件I2C的踩坑记录

RT-Thread在STM32硬件I2C的踩坑记录 0.前言一、软硬件I2C区别二、RT Thread中的I2C驱动三、尝试适配硬件I2C四、i2c-bit-ops操作函数替换五、Attention Please!六、总结 参考文章&#xff1a; 1.将硬件I2C巧妙地将“嫁接”到RTT原生的模拟I2C驱动框架 2.基于STM32F4平台的硬件I…

java八股文面试[多线程]——Synchronized的底层实现原理

笔试&#xff1a;画出Synchronized 线程状态流转实现原理图 synchronized关键字解决的是多个线程之间访问资源的同步性&#xff0c;synchronized 翻译为中文的意思是同步&#xff0c;也称之为”同步锁“。 synchronized的作用是保证在同一时刻&#xff0c; 被修饰的代码块或方…

16.CSS菜单悬停特效

效果 源码 <!DOCTYPE html> <html> <head> <title>Creative Menu Item Hover Effects</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body><section><…

(详解)数据结构-----------栈与队列 c语言实现

本章将会详细讲解以下知识点&#xff1a; 目录 一&#xff1a;栈 1&#xff1a;栈的定义&#xff0c;栈的特点 2&#xff1a;用什么结构来实现栈与原因的分析? 3: (超详解)栈的常用接口并且附上测试用例 二:队列 1:队列的定义&#xff0c;队列的特点 2&#xff1a;用什么结…

【ArcGIS微课1000例】0073:ArcGIS探索性回归分析案例

一、探索性回归工具简介 “探索性回归”工具会对输入的候选解释变量的所有可能组合进行评估,以便根据用户所指定的指标来查找能够最好地对因变量做出解释的 OLS 模型。 给定一组候选解释变量,找出正确指定的 OLS 模型: 用法: 工具还会生成一个可选表,该表包括所有满足…

Mybatis1.4 多条件查询

1.4 多条件查询 1.4.1 编写接口方法1.4.2 编写SQL语句1.4.3 编写测试方法1.4.4 动态SQL 我们经常会遇到如上图所示的多条件查询&#xff0c;将多条件查询的结果展示在下方的数据列表中。而我们做这个功能需要分析最终的SQL语句应该是什么样&#xff0c;思考两个问题 条件表达式…

SpringBoot整合JUnit、MyBatis、SSM

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 SpringBoot整合 一、SpringBoot整合JUnit二、Spri…

无人机甚高频无线电中继通讯U-ATC118

简介 甚高频无线电中继通讯系统使用经过适航认证的机载电台连接数字网络传输模块&#xff0c;通过网络远程控制无缝实现无人机操作员与塔台直接语音通话。无人机操作员可以从地面控制站远程操作机载电台进行频率切换、静噪开关、PTT按钮&#xff0c;电台虚拟面板与真实面板布局…

小程序数据导出文件

小程序josn数据生成excel文件 先从下载传送门将xlsx.mini.min.js拷贝下来&#xff0c;新建xlsx.js文件放入小程序项目文件夹下。 const XLSX require(./xlsx)//在需要用的页面中引入// 定义导出 Excel 报表的方法exportData() {const that thislet newData [{time:2021,val…

【Seata】00 - Seata Server 部署(Windows、Docker 基于 Jpom)

文章目录 前言参考目录版本说明Windows 部署 seata-server1&#xff1a;下载压缩包2&#xff1a;文件存储模式3&#xff1a;db 存储模式3.1&#xff1a;建表3.2&#xff1a;修改配置文件3.3&#xff1a;启动脚本4&#xff1a;源码部署 Docker 部署 seata-server &#xff08;基…

程序员必须掌握哪些算法?

一个程序员一生中可能会邂逅各种各样的算法&#xff0c;但总有那么几种&#xff0c;是作为一个程序员一定会遇见且大概率需要掌握的算法。今天就来聊聊这些十分重要的“必抓&#xff01;”算法吧~ 文章目录 一、程序员必须掌握哪些算法&#xff1f;二&#xff1a;常见算法介绍…