【Linux探索学习】第三十一弹——线程互斥与同步(下):深入理解确保线程安全的机制

线程互斥与同步(上):【Linux探索学习】第三十弹——线程互斥与同步(上):深入理解线程保证安全的机制-CSDN博客

Linux探索学习:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在上一篇中我们已经讲解了线程互斥与同步中关于互斥的相关知识点,今天我们来学习一下Linux中线程同步的相关知识点。其实要想做到线程同步,首先就要确保线程互斥部分是没问题的,也就是说做好线程互斥也是确保线程同步与线程安全的重要一步

在多线程编程中,线程同步是一个至关重要的问题。当多个线程共享同一资源时,如果没有合适的同步机制,可能会导致数据竞争、死锁等问题。Linux提供了多种线程同步机制,如互斥锁(前面讲过)、条件变量、读写锁、信号量等。本文将详细讲解Linux中的线程同步问题,并重点介绍POSIX信号量的使用。

目录

1. 线程同步的基本概念

1.1 什么是线程同步?

1.2 为什么需要线程同步?

2. 条件变量

2.1 条件变量的基本概念

2.2 条件变量的相关函数详解

2.2.1 pthread_cond_init

2.2.2 pthread_cond_destroy

2.2.3 pthread_cond_wait

2.2.4 pthread_cond_signal

2.2.5 pthread_cond_broadcast

2.3 条件变量的使用示例

2.4 运行结果

​编辑

2.5 代码解析

3. 读写锁

3.1 读写锁的基本概念

3.2 读写锁的相关函数详解

3.2.1 pthread_rwlock_init

3.2.2 pthread_rwlock_destroy

3.2.3 pthread_rwlock_rdlock

3.2.4 pthread_rwlock_wrlock

3.2.5 pthread_rwlock_unlock

3.3 读写锁的使用示例

3.4 运行结果

​编辑

3.5 代码解析

4. POSIX信号量

4.1 POSIX信号量的基本概念

4.2 POSIX信号量的相关函数详解

4.2.1 sem_init

4.2.2 sem_destroy

4.2.3 sem_wait

4.2.4 sem_post

4.2.5 sem_trywait

4.2.6 sem_getvalue

4.3 POSIX信号量的使用示例

4.4 运行结果

​编辑

4.5 代码解析

5. 总结

5.1 条件变量的适用场景

5.2 读写锁的适用场景

5.3 POSIX信号量的适用场景

5.4 选择同步机制的考虑因素


1. 线程同步的基本概念

1.1 什么是线程同步?

线程同步是指多个线程在访问共享资源时,通过某种机制来协调它们的执行顺序,以避免数据竞争和不一致性问题。常见的线程同步机制包括互斥锁、条件变量、读写锁、信号量等。

1.2 为什么需要线程同步?

在多线程环境中,多个线程可能会同时访问共享资源。如果没有适当的同步机制,可能会导致以下问题:

  • 数据竞争:多个线程同时修改同一数据,导致数据不一致。

  • 死锁:多个线程相互等待对方释放资源,导致程序无法继续执行。

  • 活锁:线程不断尝试获取资源但总是失败,导致程序无法继续执行。(活锁的具体讲解想了解的可以再搜一下)

2. 条件变量

2.1 条件变量的基本概念

条件变量是一种线程同步机制,用于在多个线程之间传递信号。条件变量通常与互斥锁一起使用,用于在某个条件成立时唤醒等待的线程。

2.2 条件变量的相关函数详解

2.2.1 pthread_cond_init

函数原型:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

功能:
初始化条件变量。

参数:

  • cond:指向条件变量的指针。

  • attr:条件变量的属性,通常设置为NULL,表示使用默认属性。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
2.2.2 pthread_cond_destroy

函数原型:

int pthread_cond_destroy(pthread_cond_t *cond);

功能:
销毁条件变量。

参数:

  • cond:指向条件变量的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_cond_destroy(&cond);
2.2.3 pthread_cond_wait

函数原型:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

功能:
等待条件变量,并释放互斥锁。具体点来说就是需要等待满足条件才能执行下一步,但是在这一步之前我们可能已经获取了锁,为了避免死锁问题,就需要在等待条件变量时把互斥锁释放掉

参数:

  • cond:指向条件变量的指针。

  • mutex:指向互斥锁的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_mutex_lock(&mutex);
while (!ready) {pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
2.2.4 pthread_cond_signal

函数原型:

int pthread_cond_signal(pthread_cond_t *cond);

功能:
唤醒一个等待条件变量的线程。

参数:

  • cond:指向条件变量的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_cond_signal(&cond);
2.2.5 pthread_cond_broadcast

函数原型:

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:
唤醒所有等待条件变量的线程。在使用这种方式时需要注意,因为有些时候当出现多进程的线程时,这种方法可能会导致线程误唤醒,在我们后面讲生产消费模型的时候会讲到这一点

参数:

  • cond:指向条件变量的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_cond_broadcast(&cond);

2.3 条件变量的使用示例

下面是一个简单的示例,演示了如何使用条件变量来同步两个线程的执行。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;void* thread_func(void* arg) {pthread_mutex_lock(&mutex);while (!ready) {printf("Thread waiting...\n");pthread_cond_wait(&cond, &mutex);}printf("Thread awakened!\n");pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t tid;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_create(&tid, NULL, thread_func, NULL);sleep(1);  // 模拟一些工作pthread_mutex_lock(&mutex);ready = 1;pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

2.4 运行结果

Thread waiting...
Thread awakened!

2.5 代码解析

  • pthread_cond_wait(&cond, &mutex):线程在等待条件变量时,会释放互斥锁,并在被唤醒后重新获取互斥锁。

  • pthread_cond_signal(&cond):唤醒一个等待条件变量的线程。

  • pthread_cond_broadcast(&cond):唤醒所有等待条件变量的线程。

这里的这个代码比较简单,也没有什么现实意义,重要的是先要理解一下条件变量的出现场景和使用,条件变量一般是和互斥锁同时出现的,能够确保在满足条件的情况下才能执行临界区的语句

3. 读写锁

3.1 读写锁的基本概念

读写锁是一种特殊的锁,允许多个线程同时读取共享资源,但在写操作时需要独占访问。读写锁适用于读多写少的场景,可以提高并发性能。

3.2 读写锁的相关函数详解

3.2.1 pthread_rwlock_init

函数原型:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

功能:
初始化读写锁。

参数:

  • rwlock:指向读写锁的指针。

  • attr:读写锁的属性,通常设置为NULL,表示使用默认属性。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
3.2.2 pthread_rwlock_destroy

函数原型:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:
销毁读写锁。

参数:

  • rwlock:指向读写锁的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_rwlock_destroy(&rwlock);
3.2.3 pthread_rwlock_rdlock

函数原型:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

功能:
获取读锁。

参数:

  • rwlock:指向读写锁的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_rwlock_rdlock(&rwlock);
3.2.4 pthread_rwlock_wrlock

函数原型:

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

功能:
获取写锁。

参数:

  • rwlock:指向读写锁的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_rwlock_wrlock(&rwlock);
3.2.5 pthread_rwlock_unlock

函数原型:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:
释放读写锁。

参数:

  • rwlock:指向读写锁的指针。

返回值:

  • 成功返回0,失败返回错误码。

示例:

pthread_rwlock_unlock(&rwlock);

3.3 读写锁的使用示例

下面是一个简单的示例,演示了如何使用读写锁来保护共享资源。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_rwlock_t rwlock;
int shared_data = 0;void* reader_func(void* arg) {pthread_rwlock_rdlock(&rwlock);printf("Reader: shared_data = %d\n", shared_data);pthread_rwlock_unlock(&rwlock);return NULL;
}void* writer_func(void* arg) {pthread_rwlock_wrlock(&rwlock);shared_data++;printf("Writer: shared_data = %d\n", shared_data);pthread_rwlock_unlock(&rwlock);return NULL;
}int main() {pthread_t readers[5], writers[2];pthread_rwlock_init(&rwlock, NULL);for (int i = 0; i < 5; i++) {pthread_create(&readers[i], NULL, reader_func, NULL);}for (int i = 0; i < 2; i++) {pthread_create(&writers[i], NULL, writer_func, NULL);}for (int i = 0; i < 5; i++) {pthread_join(readers[i], NULL);}for (int i = 0; i < 2; i++) {pthread_join(writers[i], NULL);}pthread_rwlock_destroy(&rwlock);return 0;
}

3.4 运行结果

Reader: shared_data = 0
Reader: shared_data = 0
Reader: shared_data = 0
Reader: shared_data = 0
Reader: shared_data = 0
Writer: shared_data = 1
Writer: shared_data = 2

3.5 代码解析

  • pthread_rwlock_rdlock(&rwlock):获取读锁,允许多个线程同时读取共享资源。

  • pthread_rwlock_wrlock(&rwlock):获取写锁,独占访问共享资源。

  • pthread_rwlock_unlock(&rwlock):释放读写锁。

读写锁出现的场景还是比较多的,尤其是当写少读多的时候,读写锁可以帮助我们提高并发效率

4. POSIX信号量

4.1 POSIX信号量的基本概念

POSIX信号量是一种用于线程同步的机制,可以用来控制对共享资源的访问。信号量有一个整数值,表示可用资源的数量。线程可以通过sem_waitsem_post来分别减少和增加信号量的值。

4.2 POSIX信号量的相关函数详解

4.2.1 sem_init

函数原型:

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:
初始化信号量。

参数:

  • sem:指向信号量的指针。

  • pshared:指定信号量的类型,0表示线程间共享,非0表示进程间共享。

  • value:信号量的初始值。

返回值:

  • 成功返回0,失败返回-1。

示例:

sem_t sem;
sem_init(&sem, 0, 0);
4.2.2 sem_destroy

函数原型:

int sem_destroy(sem_t *sem);

功能:
销毁信号量。

参数:

  • sem:指向信号量的指针。

返回值:

  • 成功返回0,失败返回-1。

示例:

sem_destroy(&sem);
4.2.3 sem_wait

函数原型:

int sem_wait(sem_t *sem);

功能:
减少信号量的值,如果信号量的值为0,则阻塞。

参数:

  • sem:指向信号量的指针。

返回值:

  • 成功返回0,失败返回-1。

示例:

sem_wait(&sem);
4.2.4 sem_post

函数原型:

int sem_post(sem_t *sem);

功能:
增加信号量的值,并唤醒等待的线程。

参数:

  • sem:指向信号量的指针。

返回值:

  • 成功返回0,失败返回-1。

示例:

sem_post(&sem);
4.2.5 sem_trywait

函数原型:

int sem_trywait(sem_t *sem);

功能:
尝试减少信号量的值,如果信号量的值为0,则立即返回错误。

参数:

  • sem:指向信号量的指针。

返回值:

  • 成功返回0,失败返回-1。

示例:

sem_trywait(&sem);
4.2.6 sem_getvalue

函数原型:

int sem_getvalue(sem_t *sem, int *sval);

功能:
获取信号量的当前值。

参数:

  • sem:指向信号量的指针。

  • sval:指向存储信号量值的整数的指针。

返回值:

  • 成功返回0,失败返回-1。

示例:

int value;
sem_getvalue(&sem, &value);

4.3 POSIX信号量的使用示例

下面是一个简单的示例,演示了如何使用POSIX信号量来同步两个线程的执行。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>sem_t sem;void* thread_func(void* arg) {printf("Thread waiting...\n");sem_wait(&sem);printf("Thread awakened!\n");return NULL;
}int main() {pthread_t tid;sem_init(&sem, 0, 0);pthread_create(&tid, NULL, thread_func, NULL);sleep(1);  // 模拟一些工作printf("Main thread signaling...\n");sem_post(&sem);pthread_join(tid, NULL);sem_destroy(&sem);return 0;
}

4.4 运行结果

Thread waiting...
Main thread signaling...
Thread awakened!

4.5 代码解析

  • sem_wait(&sem):减少信号量的值,如果信号量的值为0,则阻塞。

  • sem_post(&sem):增加信号量的值,并唤醒等待的线程。

我们在前面讲进程的时候就讲过信号量的知识了,这里基本没啥大的差别,关于POSIX信号量的使用,我们在下一章讲生产消费模型的时候也会再详细讲解的,这里大家先理解一下基本用法即可

5. 总结

本文详细讨论了Linux中线程同步的几种机制,包括条件变量、读写锁和POSIX信号量。通过简单的代码示例,我们演示了如何使用这些机制来同步多个线程的执行。在实际的多线程编程中,选择合适的同步机制非常重要,可以有效避免数据竞争、死锁等问题。

5.1 条件变量的适用场景

条件变量适用于需要等待某个条件成立的场景,通常与互斥锁一起使用。例如,生产者-消费者模型中的缓冲区满或空的情况。

5.2 读写锁的适用场景

读写锁适用于读多写少的场景,可以提高并发性能。例如,数据库系统中的读操作远多于写操作。

5.3 POSIX信号量的适用场景

POSIX信号量适用于需要控制对共享资源访问的场景。例如,限制同时访问某个资源的线程数量。

5.4 选择同步机制的考虑因素

在选择线程同步机制时,需要考虑以下因素:

  • 性能:不同的同步机制对性能的影响不同,需要根据具体场景选择最合适的机制。

  • 复杂性:某些同步机制的使用较为复杂,需要仔细设计和测试。

  • 可维护性:选择易于理解和维护的同步机制,可以减少代码的复杂性和出错的可能性。

总之,线程的同步是确保线程安全很重要的一步,今天我们讲解了确保线程同步机制的重要的几个方面,关于这几个方面的知识点的应用在下一篇生成消费模型还会仔细讲解一遍

本篇笔记:

感谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!

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

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

相关文章

《Effective Objective-C》阅读笔记(中)

目录 接口与API设计 用前缀避免命名空间冲突 提供“全能初始化方法” 实现description方法 尽量使用不可变对象 使用清晰而协调的命名方式 方法命名 ​编辑类与协议命名 为私有方法名加前缀 理解OC错误模型 理解NSCopying协议 协议与分类 通过委托与数据源协议进行…

python-leetcode-每日温度

739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; class Solution:def dailyTemperatures(self, temperatures: List[int]) -> List[int]:n len(temperatures)answer [0] * nstack [] # 存储索引for i, temp in enumerate(temperatures):while stack and temperat…

山东大学软件学院nosql实验三

实验题目&#xff1a; 用Java做简单查询(2学时) 实验内容 用API方式&#xff0c;做简单查询。 实验要求 在以下要求中选择至少2个&#xff0c;使用Java语言实现数据查询&#xff0c;最终把数据输出到前端界面。 &#xff08;1&#xff09;找出年龄小于20岁的所有学生 &…

【Linux】初探信号的奥秘

目录 一、引入信号&#xff1a; 1、什么是信号&#xff1a; 二、前后台进程&#xff1a; 三、信号的处理方式&#xff1a; 四、键盘数据与信号&#xff1a; 前言&#xff1a; 在Linux系统编程中&#xff0c;信号&#xff08;Signal&#xff09;是一种至关重要的进程间通信…

Bugku CTF CRYPTO

Bugku CTF CRYPTO 文章目录 Bugku CTF CRYPTO聪明的小羊ok[-<>]散乱的密文.!? 聪明的小羊 描 述: 一只小羊翻过了2个栅栏 fa{fe13f590lg6d46d0d0} 分 析&#xff1a;栅栏密码&#xff0c;分2栏&#xff0c;一个栏里有11个 ①手动解密 f a { f e 1 3 f 5 9 0 l g 6 d 4 …

数据库的基本操作

目录 一、查看所有的数据库&#xff1a; 二、创建数据库&#xff1a; if not exists : 字符编码集&#xff1a; 排序规则&#xff1a; 三、查看创建的库&#xff1a; 四、修改数据库&#xff1a; 五、删除数据库&#xff1a; if exists&#xff1a; 前言&#xff1a; 在…

IDEA集成DeepSeek,通过离线安装解决无法安装Proxy AI插件问题

文章目录 引言一、安装Proxy AI1.1 在线安装Proxy AI1.2 离线安装Proxy AI 二、Proxy AI中配置DeepSeek2.1 配置本地部署的DeepSeek&#xff08;Ollama方式&#xff09;2.2 通过第三方服务商提供的API进行配置 三、效果测试 引言 许多开发者尝试通过安装Proxy AI等插件将AI能力…

Java+SpringBoot+Vue+数据可视化的音乐推荐与可视化平台(程序+论文+讲解+安装+调试+售后)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在互联网技术以日新月异之势迅猛发展的浪潮下&#xff0c;5G 通信技术的普及、云计算能力…

【时时三省】(C语言基础)常量和变量

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 在计算机高级语言中&#xff0c;数据有两种表现形式&#xff1a;常量和变量。 常量 在程序运行过程中&#xff0c;其值不能被改变的量称为常量。数值常量就是数学中的常数。 常用的常量有以…

zabbix故障案例 WEB页面Database error Connection refused

目录 1.思路 2.问题解决 3.其他数据库问题思路 1.思路 当我们遇到 Database error Connection refused的问题的时候一般想到的都是数据库的问题 那我们这时候就顺着这条线索排查 2.问题解决 我们首先先进入数据库 mysql -uzabbix -p123 发现了如下报错 应该是数…

MaxKB+Ollama+DeepSeek1.5B部署知识库

环境信息 练习测试用&#xff0c;所以资源很低&#xff0c;8G显卡。大模型部署在Windows台式机上&#xff0c;MaxKB部署在CentOS虚拟机上。 台式机&#xff1a; 硬件&#xff1a;i7 13900 NV GeForce RTX 3060 Ti 8G显存 32G内存 软件&#xff1a;Windows 11操作系统&…

猿大师播放器:智慧交通Web网页低延迟播放监控RTSP H.265视频解决方案

在智慧城市建设加速推进的今天&#xff0c;智慧交通作为城市"神经系统"正面临前所未有的发展机遇。据统计&#xff0c;2023年全国交通视频监控设备保有量已突破4500万台&#xff0c;日均产生的视频数据量超50PB。但在这些庞大数字背后&#xff0c;行业却普遍面临着&q…

Web3.py 入门笔记

Web3.py 学习笔记 &#x1f4da; 1. Web3.py 简介 &#x1f31f; Web3.py 是一个 Python 库&#xff0c;用于与以太坊区块链进行交互。它就像是连接 Python 程序和以太坊网络的桥梁。 官方文档 1.1 主要功能 查询区块链数据&#xff08;余额、交易等&#xff09;发送交易与…

如何选择工控产线安全软件?

在当今数字化时代&#xff0c;信息安全的重要性不言而喻。随着工业控制系统&#xff08;ICS&#xff09;的广泛应用&#xff0c;主机的安全加固成为了保障企业生产运营稳定的关键环节。MCK-T主机加固系统软件&#xff0c;凭借其卓越的性能和全面的安全防护功能&#xff0c;成为…

系统调用过程

注意&#xff1a;本系统调用过程基于32位操作系统 中断服务程序的寻址过程 1.用户态程序产生系统调用write()&#xff1b; 2.产生中断指令ENTER_KERNEL(int $0x80128)&#xff0c;CPU收到中断指令去查询中断向量表&#xff0c;找出中断号0x80对应的中断服务程序的内存基地址(0…

PHP入门基础学习七(函数3)

九、数组函数 1、合并两个数组 合并两个数组,其中一个当健名,一个当值 注意: array_combine 函数,通过合并两个数组来创建一个新数组,其中的一个数组是键名,另一个数组的值为键值。 2.1、排序函数 对于数组的排序,除了可使用前面讲解的排序算法实现外,PHP还提供了内置…

pycharm管理虚拟环境

不借用Anoconda 1.检查pip所在位置&#xff0c; 因为pip的默认安装路径是python的安装目录下的依赖库路径D:\Program Files\Python397\Lib\site-packages。项目如果用之前pycharm创建的环境是无法加载这个路径的库的。 2.安装时指定安装路径 千万要注意指定安装路径为项目的…

DeepSeek 助力 Vue 开发:打造丝滑的 复选框(Checkbox)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

FMT源码 - module

module 功能模块 1、uMCN uMCN 是 类似于 PX4里面的 uORB 模块。 mcn listmcn echo sensor_imu0mcn echo <topic> [options]options:-n, --number Set topic echo number, e.g, -n 10 will echo 10 times. (朝终端打印的次数)-p, --period Set topic echo peri…

城电科技|会追日的智能花,光伏太阳花开启绿色能源新篇章

当艺术与科技相遇&#xff0c;会碰撞出怎样的火花&#xff1f;城电科技推出的光伏太阳花&#xff0c;以其独特的设计与智能化的功能&#xff0c;给出了答案。这款产品不仅具备太阳能发电的实用功能&#xff0c;更是一件充满科技属性的艺术性光伏产品&#xff0c;吸引了广泛关注…