Linux 线程安全 (2)

文章目录

  • 线程同步概念
  • 条件变量使用
  • 生产消费模型
  • 信号量的使用
  • 读写锁的使用

Linux 线程安全 (1)

线程同步概念

竞态条件:因为时序问题,而导致程序异常.

饥饿问题:只使用互相锁保证线程安全时,锁资源总被某一个线程占用的情况。

线程同步:线程同步是指在多线程编程中,为了保证临界资源的正确访问和避免竞态条件,需要协调和控制线程之间的执行顺序和互斥访问。让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。常见的线程同步机制包括互斥锁(Mutex)、信号量(Semaphore)、条件变量(Condition Variable)等。

条件变量使用

在多线程编程中,有需要等待某个资源就绪,线程才能开始正常运行的需求。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

条件变量是一种线程同步的高级机制,它可以实现线程的等待和唤醒操作。条件变量通常与互斥锁一起使用,当某个条件不满足时,线程可以调用条件变量的等待操作进入等待状态,当条件满足时,其他线程可以调用条件变量的唤醒操作来唤醒等待的线程。

// 1.初始化条件变量 
//cond为要初始化的条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
//2.等待条件满足 
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
//3.唤醒等待
//唤醒所有在等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//随机唤醒一个真在等待的线程
int pthread_cond_signal(pthread_cond_t *cond);

例子

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

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1( void *arg )
{
	while ( 1 )
	{
		pthread_cond_wait(&cond, &mutex);
		printf("活动\n");
	}
}

void *r2(void *arg )
{
	while ( 1 ) 
	{
		pthread_cond_signal(&cond);
		sleep(1);
	}
}

int main( void )
{
	pthread_t t1, t2;
	pthread_cond_init(&cond, NULL);
	pthread_mutex_init(&mutex, NULL);

	pthread_create(&t1, NULL, r1, NULL);
	pthread_create(&t2, NULL, r2, NULL);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
}

条件变量实际使用规范
①等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

②给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

"条件” 可以理解为需要等待就绪的资源。
条件为假:线程代码运行需要的资源未就绪。
条件为真:线程代码运行需要的资源就绪。
修改条件:使用资源。

生产消费模型

产者消费者模型是一种常见的并发编程模型,用于解决多线程环境下的生产者和消费者之间的协作问题。在这个模型中,生产者负责生产数据,并将数据放入共享的缓冲区中,而消费者则负责从缓冲区中取出数据并进行消费。

该模型的基本思想是通过一个共享的缓冲区来实现生产者和消费者之间的同步与通信。生产者在生产数据之前会检查缓冲区是否已满,如果已满则等待,直到有空闲位置。而消费者在消费数据之前会检查缓冲区是否为空,如果为空则等待,直到有数据可供消费。

为了实现这种同步与通信,可以使用互斥锁(mutex)来保护缓冲区的访问,以及条件变量(condition variable)来实现生产者和消费者之间的等待和通知机制。

在这里插入图片描述

信号量的使用

此处的信号量使用指的是对POSIX信号量的使用,POSIX信号量可以用于线程间同步。

//1.初始化信号量
//pshared=0表示线程间共享信号量
//value 表示信号量的初始值
int sem_init(sem_t *sem, int pshared, unsigned int value);
//2.等待信号量,会将信号量的值减
int sem_wait(sem_t *sem);
//3.发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1
int sem_post(sem_t *sem);
//4.销毁信号量
int sem_destroy(sem_t *sem);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define NUM_THREADS 5

sem_t semaphore;

void* thread_function(void* arg) 
{
    int thread_id = *(int*)arg;
    
    printf("Thread %d is waiting...\n", thread_id);
    sem_wait(&semaphore);
    printf("Thread %d has acquired the semaphore.\n", thread_id);
    
    // 模拟线程执行一些任务
    sleep(2);
    
    printf("Thread %d is releasing the semaphore.\n", thread_id);
    sem_post(&semaphore);
    
    pthread_exit(NULL);
}

int main() 
{
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    
    // 初始化信号量,初始值为1
    sem_init(&semaphore, 0, 1);
    
    // 创建多个线程
    for (int i = 0; i < NUM_THREADS; i++) 
    {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
    }
    
    // 等待所有线程结束
    for (int i = 0; i < NUM_THREADS; i++) 
    {
        pthread_join(threads[i], NULL);
    }
    
    // 销毁信号量
    sem_destroy(&semaphore);
    
    return 0;
}

读写锁的使用

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。 为了专门处理这种情况,有了读写锁。

使用

// 1. 设置读写优先级
// attr指向要设置属性的读写锁的指针
// pref 共有3种选择
//PTHREAD_RWLOCK_PREFER_READER_NP:优先选择读者。
//PTHREAD_RWLOCK_PREFER_WRITER_NP:优先选择写者。
//PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP:优先选择非递归写者。
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, 
int pref);

//2. 初始化
//rwlock用于指定要初始化的读写锁对象。
//attr设置为nullptr
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);

//3. 加锁和解锁

/*pthread_rwlock_rdlock函数用于获取读写锁的读取锁定。
读取锁定允许多个线程同时持有锁,只要没有线程持有写入锁。
如果有线程持有写入锁,则其他线程尝试获取读取锁会被阻塞,直到写入锁被释放。*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

/*
pthread_rwlock_wrlock函数用于获取读写锁的写入锁定。
写入锁定是独占的,只能由一个线程持有。如果有线程持有读取锁或写入锁,
则其他线程尝试获取写入锁会被阻塞,直到所有读取锁和写入锁都被释放
*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

//用于释放读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

实际使用:

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

// 共享资源
int shared_data = 0;

// 读写锁
pthread_rwlock_t rwlock;

// 读线程函数
void* reader_function(void* arg) 
{
    int thread_id = *(int*)arg;
    
    while (1) 
    {
        // 获取读锁
        pthread_rwlock_rdlock(&rwlock);
        
        // 读取共享资源
        printf("Reader %d reads shared data: %d\n", thread_id, 
        shared_data);
        
        // 释放读锁
        pthread_rwlock_unlock(&rwlock);
        
        // 等待一段时间
        sleep(1);
    }
    
    pthread_exit(NULL);
}

// 写线程函数
void* writer_function(void* arg) 
{
    int thread_id = *(int*)arg;
    
    while (1) 
    {
        // 获取写锁
        pthread_rwlock_wrlock(&rwlock);
        
        // 修改共享资源
        shared_data++;
        printf("Writer %d updates shared data: %d\n", thread_id, 
        shared_data);
        
        // 释放写锁
        pthread_rwlock_unlock(&rwlock);
        
        // 等待一段时间
        sleep(2);
    }
    
    pthread_exit(NULL);
}

int main() 
{
    pthread_t readers[3];
    pthread_t writers[2];
    int reader_ids[3] = {1, 2, 3};
    int writer_ids[2] = {1, 2};
    
    // 初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);
    
    // 创建读线程
    for (int i = 0; i < 3; i++) 
    {
        pthread_create(&readers[i], NULL, reader_function, 
        &reader_ids[i]);
    }
    
    // 创建写线程
    for (int i = 0; i < 2; i++) 
    {
        pthread_create(&writers[i], NULL, writer_function, 
        &writer_ids[i]);
    }
    
    // 等待读线程结束
    for (int i = 0; i < 3; i++) 
    {
        pthread_join(readers[i], NULL);
    }
    
    // 等待写线程结束
    for (int i = 0; i < 2; i++) 
    {
        pthread_join(writers[i], NULL);
    }
    
    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);
    
    return 0;
}

在这里插入图片描述

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

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

相关文章

听说上海移动年终奖16个月!我承认我酸了!

* 你好&#xff0c;我是前端队长&#xff0c;在职场&#xff0c;玩副业&#xff0c;文末有福利! 今天&#xff0c;队长看到一篇帖子&#xff0c;有网友发帖说上海移动的年终奖发了16个月&#xff0c;我承认我酸了。 看到这里&#xff0c;我承认我也酸了。16个月是什么概念&…

案例-旋转的太极图案(HTML+CSS)

使用css的动画变换效果完成“ 旋转太极“。 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>*{margin: 0;padding: 0;background-color: antiquewhite;}.tj{width: 0;height: 300px;/* border…

CEC2017(Python):五种算法(PSO、RFO、SSA、DE、HHO)求解CEC2017

一、5种算法简介 1、粒子群优化算法PSO 2、红狐优化算法RFO 3、麻雀搜索算法SSA 4、差分进化算法DE 5、哈里斯鹰优化算法HHO 二、CEC2017简介 参考文献&#xff1a; [1]Awad, N. H., Ali, M. Z., Liang, J. J., Qu, B. Y., & Suganthan, P. N. (2016). “Problem de…

计算机网络【EPoll原理】

预备知识&#xff1a;内核poll钩子原理 内核函数poll_wait 把当前进程加入到驱动里自定义的等待队列上 &#xff1b; 当驱动事件就绪后&#xff0c;就可以在驱动里自定义的等待队列上唤醒调用poll的进程&#xff1b; 故poll_wait作用&#xff1a;可以让驱动知道事件就绪的时…

蛇目标检测数据集VOC格式100张

蛇是一种广泛分布于地球各个角落的爬行动物&#xff0c;是无脚类爬行动物中最为特殊的一类。它们身体长而细长&#xff0c;通常由许多鳞片组成&#xff0c;没有四肢。蛇生活的环境非常多样&#xff0c;可以在沙漠、森林、草原和水域等各种地方找到它们的踪迹。 蛇是以捕食其他…

VS2013中特殊操作

代码段管理器(查看代码补全快捷方式) 1.点击 工具 ->点击 代码片段管理器->看到 语言->选择 Visual C 2.可以点击下方添加 自定义一个属于自己的快捷代码补全方式 3.结果图&#xff1a; 设置自动换行与行号 1.点击 工具->点击 选项->找到 文本编辑器(然后点击)…

百度地图添加坐标点

​​​​​​html <!DOCTYPE html><html xmlns"http://www.w3.org/1999/xhtml"> <head runat"server"><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title>查看签到信息-地图…

[蓝桥杯2022省赛] X 图形

X 图形 问题描述 给定一个字母矩阵。一个 X 图形由中心点和由中心点向四个 4545 度斜线方向引出的直线段组成&#xff0c;四条线段的长度相同&#xff0c;而且四条线段上的字母和中心点的字母相同。 一个 X 图形可以使用三个整数r,c,L 来描述&#xff0c;其中 r,c 表示中心点…

stm32中的i2c协议

stm32中I2C 文章目录 stm32中I2CI2C 协议简介I2C物理层协议层I2C基本读写过程 **通讯的起始和停止信号****数据有效性****地址及数据方向****响应** STM32的I2C特性及架构**STM32** **的** I2C外设简介STM32 的 I 2C 架构剖析通讯引脚 通讯过程主发送器主接收器 I2C初始化结构体…

工程(十六)——自己数据集跑Fast_livo

一、基础环境 Ubuntu20.04 ROS noetic PCL 1.8 Eigen 3.3.4 Sophus git clone https://github.com/strasdat/Sophus.git cd Sophus git checkout a621ff mkdir build && cd build && cmake .. make sudo make install 下面两个直接把包下载下来一起编译…

swing快速入门(三十二)消息对话框

注释很详细&#xff0c;直接上代码 新增内容 1.自定义对话框前列图标 2.消息对话框的若干种形式 package swing21_30;import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent;public class swing_test_30 {// 定义一个JFrameJFrame jFrame new JFram…

React 、Vue进度 条首屏加载制作

React 大家都听说过&#xff0c;是一个非常出名的前端 框架 &#xff0c;目前在公司 用的比较多的两个前端框架 &#xff0c;一个是 React , 一个 是 Vue 2 、3 &#xff0c;公司 的首页 &#xff0c;后台 前端部分 都是 以这两个为主 &#xff0c;做了 不下 数十个 项目 但是 …

大数据背后的绿色收割:基于Hadoop的农产品价格信息智能分析

大数据背后的绿色收割&#xff1a;基于Hadoop的农产品价格信息智能分析 引言正文1. 数据获取与准备2. 数据清洗与处理3. Hadoop数据分析引擎的运用4. MySQL数据库的集成5. 创新性的可视化6. 结论与展望 结语 引言 随着信息技术的不断发展&#xff0c;农业领域也在数字化的浪潮…

【2023年终总结】纵是一路仆仆风尘,也莫忘了仰头

文章目录 1. 写在前面2. 关于生活3. 关于工作4. 关于以后 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋…

阿里云2核2G3M服务器放几个网站?

阿里云2核2g3m服务器可以放几个网站&#xff1f;12个网站&#xff0c;阿里云服务器网的2核2G服务器上安装了12个网站&#xff0c;甚至还可以更多&#xff0c;具体放几个网站取决于网站的访客数量&#xff0c;像阿里云服务器网aliyunfuwuqi.com小编的网站日访问量都很少&#xf…

三路电源互备自投电路

当供电源停电时&#xff0c;主备用电源自动投入运行&#xff0c;当主备用电源断电时&#xff0c;则次备用电源自动投入运行&#xff0c;从而大大提高供电的可靠性。

基于three.js的室内全景3D展馆案例分享

先看效果图 实现了第一人称行走&#xff0c;WASD点击目标画册进行预览查看位置音乐播放环绕地面镜面反光碰撞检测等等. 地址在gitee上gallery: 数字展馆概念的demo项目&#xff0c;本项目中使用的技术栈为three.js 有兴趣的伙伴可以去下载看看&#xff0c;有这方面的项目应该能…

Python FastApi连接oracle进行查询

这边技术选型是cx_oracle进行连接查询&#xff0c;cx_oracle的使用首先要有官方的客户端才能连接到数据库&#xff0c;python并不自带客户端。我用是Python3.9 安装客户端 可以到官网在选择最新版进行下载。 Instant Client for Microsoft Windows (x64) 64-bit 或者直接从我…

maven命令行安装依赖测试

mvn dependency:get -DgroupIdorg.springframework -DartifactIdspring-core -Dversion5.3.9作用&#xff1a;可用于测试配置环境变量后&#xff0c;能否下载依赖到本地仓库

【SpringCloud】从实际业务问题出发去分析Eureka-Server端源码

文章目录 前言1.EnableEurekaServer2.初始化缓存3.jersey应用程序构建3.1注册jeseryFilter3.2构建JerseyApplication 4.处理注册请求5.registry&#xff08;&#xff09; 前言 前段时间遇到了一个业务问题就是k8s滚动发布Eureka微服务的过程中接口会有很多告警&#xff0c;当时…