《Linux C编程实战》笔记:线程同步

news2024/9/25 4:27:48

这一节主要是解决共享资源的处理。操作系统里也讲过互斥、锁之类的概念。

互斥锁

互斥锁通过锁机制来实现线程同步,同一时刻只允许一个线程执行一个关键部分的代码

一下是操作互斥锁的函数,均声明在pthread.h中。

  1. pthread_mutex_init(初始化互斥锁)

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

    该函数用于初始化互斥锁,其中mutex是指向互斥锁的指针,attr是指向互斥锁属性的指针。如果不需要特殊属性,可以将attr参数设置为NULL

  2. pthread_mutex_destroy(销毁互斥锁)

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    该函数用于销毁互斥锁,释放相关资源。在互斥锁使用完毕后,应该调用该函数来进行清理操作。函数成功执行时返回0.

  3. pthread_mutex_lock(加锁)

    int pthread_mutex_lock(pthread_mutex_t *mutex);

    该函数用于尝试获得互斥锁的所有权。如果互斥锁已经被其他线程占用,调用线程将被阻塞,直到互斥锁可用。

  4. pthread_mutex_unlock(解锁)

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    该函数用于释放互斥锁(与加锁的必须是同一线程),允许其他线程获得该互斥锁的所有权。应该在对共享资源的访问完成后调用该函数。

  5. pthread_mutex_trylock(尝试加锁)

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

    该函数尝试获得互斥锁的所有权,但如果锁已经被其他线程占用,则不会阻塞,而是立即返回错误EBUSY。可以利用这个函数来实现非阻塞的加锁操作。

互斥锁的初始化可以用静态赋值法,将一个宏结构赋给mutex

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

或者是用初始化函数,不过用之前还要先了解pthread_mutexattr_t怎么用。

    pthread_mutexattr_t mutexattr;
    pthread_mutexattr_init(&mutexattr);
    pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE);

这样算是设定好了锁的属性。属性一般是以下几种

  1. PTHREAD_MUTEX_NORMAL

    • 普通互斥锁,不允许递归锁。对同一线程多次调用 pthread_mutex_lock 会导致死锁。
  2. PTHREAD_MUTEX_RECURSIVE

    • 允许同一线程多次获得互斥锁,使用嵌套锁。线程每成功调用一次 pthread_mutex_lock,必须调用相同次数的 pthread_mutex_unlock 才能释放锁。
    • 如果是不同线程请求,则在解锁时重新竞争
  3. PTHREAD_MUTEX_ERRORCHECK

    • 提供错误检查,如果同一线程重复调用 pthread_mutex_lock,会返回错误EDEADLK。
  4. PTHREAD_MUTEX_DEFAULT

    • 默认类型,通常等同于 PTHREAD_MUTEX_NORMAL

注意:加锁时,不论哪种类型的锁,都不可能被两个不同的线程同时得到,其中一个必须等待解锁。在同一进程中的线程,如果加锁后没有解锁,则其他线程将无法再获得该锁。

示例代码

以下代码演示了互斥锁保护全局变量的用法

pthread_mutex_t number_mutex;
int globalnumber;
void write_globalnumber(){
    pthread_mutex_lock(&number_mutex);
    globalnumber++;
    pthread_mutex_unlock(&number_mutex);
}
int read_globalnumber(){
    int temp;
    pthread_mutex_lock(&number_mutex);
    temp=globalnumber;
    pthread_mutex_unlock(&number_mutex);
    return temp;
}

还是很好懂的,在读写之前上锁,操作完之后解锁。

条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制。 条件变量宏观上类似if语句,
符合条件就能执行某段程序,否则只能等待条件成立。

使用条件变量主要包括两个动作:一个等待使用资源的线程等待“条件变量被设置为真”;另一个线程在使用完资源后“设置条件为真”,这样就可以保证线程间的同步了。这样就存在一个关键问题,就是要保证条件变量能被正确的修改,条件变量要收到特殊的保护。实际使用中互斥锁扮演者这样的一个保护者的角色。Linux提供了一系列对条件变量操作的函数

  1. pthread_cond_init:

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

    该函数用于初始化条件变量。pthread_cond_t 是条件变量的类型,attr 是条件变量的属性,通常可以设置为 NULL 表示默认属性。

  2. pthread_cond_destroy:

    int pthread_cond_destroy(pthread_cond_t *cond);

    该函数用于销毁条件变量。在条件变量不再使用时调用,以释放相关资源。

  3. pthread_cond_wait:

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

    这个函数使线程等待条件变量的信号。在调用这个函数之前,线程通常需要先获取一个互斥锁(mutex),然后在等待条件变量的时候会释放这个互斥锁。

  4. pthread_cond_timedwait:

    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

    类似于 pthread_cond_wait,但是它在指定的时间(abstime)之后超时返回。abstime 是一个绝对时间。

  5. pthread_cond_signal:

    int pthread_cond_signal(pthread_cond_t *cond);

    该函数用于发送信号给一个正在等待条件变量的线程,唤醒其中一个。通常在某个条件变为真的时候调用。

  6. pthread_cond_broadcast:

    int pthread_cond_broadcast(pthread_cond_t *cond);

    该函数用于发送信号给所有正在等待条件变量的线程,唤醒它们。通常在某个条件变为真的时候调用。

和互斥锁一样,条件变量的初始化也可以用静态赋值法

pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

还有一种方式自然是用初始化函数,一般attr都是空指针

pthread_cond_wait函数书房由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态。

线程被条件变量阻塞后,可通过两个函数激活。

signal只激活一个等待的线程,存在多个的话按入队顺序激活;broadcast激活所有等待的线程

清除函数destory只有在没有线程等待该条件变量的时候才能清除,否则返回EBUSY

说了这么多,其实也不知道应该怎么用,看下面的代码

示例代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void cleanup_handler(void *arg) {
    pthread_mutex_unlock((pthread_mutex_t *)arg);
}
void *thread1(void *arg){
    pthread_cleanup_push(cleanup_handler,&mutex);//这个在上一篇文章有讲过
    while (1)
    {
        printf("thread1 is running\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        printf("thread1 applied the condition\n");
        pthread_mutex_unlock(&mutex);
        sleep(4);
    }
    
    pthread_cleanup_pop(0);

}
void *thread2(void *arg){
    while(1){
        printf("thread2 is running\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        printf("thread2 applied the condition\n");
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}
int main(){
    pthread_t tid1,tid2;
    printf("condition variable study!\n");
    pthread_mutex_init(&mutex,nullptr);
    pthread_cond_init(&cond,nullptr);
    pthread_create(&tid1,nullptr,thread1,nullptr);
    pthread_create(&tid2,nullptr,thread2,nullptr);
    do{
        pthread_cond_signal(&cond);
    }while (1);
    
}

执行片段

这个具体是怎么执行的呢,首先线程1给mutex上锁然后执行pthread_cond_wait,这时候mutex就解锁了,同时线程1或阻塞,所以线程2先打印了applied这句话,然后cond会收到主线程发来的signal信号,这时候线程1就被唤醒了,它也继续执行。

其实最好来说,pthread_mutex_unlock不需要主动调用,因为wait函数本身就会解锁了,而且这样反而有可能会破互斥量的上锁情况。所以可以把unlock那两行注释掉。

clean的那段代码考虑的是上锁但是wait函数被取消的情况,这种情况下会一直上锁,所以让退出回调函数解锁,有备无患。

条件变量都是需要配合互斥量一起使用的,这样可以防止多个线程请求wait函数

还有一小节是关于异步信号的,不过书上就讲了几行,估计不太重要吧

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

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

相关文章

【2024济南生物发酵展同期会议】合成生物学背景下的发酵深层次技术论坛

2024合成生物学背景下的发酵深层次技术论坛 新技术、新资源、新机遇 反应设备.过滤分离.提取浓缩.干燥.流体机械.实验室设备.仪器仪表.废水废气 主办单位&#xff1a; 生物发酵展组委会 发酵人社区公众号 万物生物合成俱乐部 承办单位&#xff1a; 上海履济技术服务中心 …

在 WinForms 应用中使用 FtpWebRequest 进行文件操作和数据显示

在 WinForms 应用中使用 FtpWebRequest 进行文件操作和数据显示 引言 在企业级应用或桌面程序中&#xff0c;经常需要从远程服务器获取数据&#xff0c;并在用户界面上展示这些数据。本文将通过一个实际案例&#xff0c;演示如何在 Windows Forms 应用程序中使用 FtpWebReques…

openWrt将插件安装到USB外接硬盘上

问题描述&#xff1a; 陆由器的闪存空间不够&#xff0c;而陆由器有一个usb接口&#xff0c;可以外接硬盘&#xff0c;可以将插件安装在外接硬盘上&#xff0c;就再也不用担心陆由器的空间不够了&#xff1b; 解决方案&#xff1a; 查看USB目录&#xff0c;为 mnt/sdb1 利用…

Web前端-移动web开发_流式布局

文章目录 移动web开发流式布局1.0 移动端基础1.1浏览器现状1.2 手机屏幕的现状1.3常见移动端屏幕尺寸1.4移动端调试方法 2.0 视口2.1 布局视口 layout viewport2.2视觉视口 visual viewport2.3理想视口 ideal viewport&#xff08;苹果&#xff09;2.4meta标签 3.0 物理像素(手…

kafka入门(六):日志分段(LogSegment)

日志分段&#xff08;LogSegment&#xff09; Kafka的一个 主题可以分为多个分区。 一个分区可以有一至多个副本&#xff0c;每个副本对应一个日志文件。 每个日志文件对应一个至多个日志分段&#xff08;LogSegment&#xff09;。 每个日志分段还可以细分为索引文件、日志存储…

MOOSE相关滤波跟踪算法(个人学习笔记)

MOOSE 论文标题 “Visual Object Tracking using Adaptive Correlation Filters” 原文地址 用滤波器对目标外观进行建模&#xff0c;并通过卷积操作来执行跟踪。 参考阅读&#xff1a; 目标跟踪经典算法——MOSSE&#xff08;Minimum Output Sum Square Error&#xff09…

Redis命令总结

1、启动Redis服务&#xff0c;登录Redis # 开启redis服务 redis-server redis配置文件路径例子&#xff1a; redis-server redis.windows.conf# 连接redis 【无密码】 redis-cli# 连接redis【有密码】 # 1 先连接再输入密码 redis-cli auth 密码 2、连接时输入 IP址、端口号、…

GC6153步进电机驱动芯片——低噪声、低振动,应用于摄像机,机器人等产品上

GC6153是双通道5V低压步进电机驱动器具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦和对焦系统&#xff0c;万向节&#xff0c;摇头机和其他精密&#xff0c;低噪声扫描隧道显微镜控制系统。该芯片为每个通道集成了256微步驱动器通过SPI和I2C接口&#xff0c;用户可…

【模型评估 07】过拟合与欠拟合

在模型评估与调整的过程中&#xff0c;我们往往会遇到“过拟合”或“欠拟合”的情况。如何有效地识别“过拟合”和“欠拟合”现象&#xff0c;并有针对性地进行模型调整&#xff0c;是不断改进机器学习模型的关键。特别是在实际项目中&#xff0c;采用多种方法、从多个角度降低…

USB_CH340一键下载电路

目录标题 1、CH340概述2、CH340芯片特点3、CH340系列芯片4、CH340引脚定义5、CH340传统的一键下载电路5.1、Stm32串口下载5.2、ESP32串口下载5.3、注意 6、免外围电路下载 1、CH340概述 CH340是一个USB总线的转接芯片&#xff0c;可实现USB转串口或者USB转打印口。 2、CH340芯…

高级分布式系统-第7讲 分布式系统的时钟同步

顺序的分类 在分布式系统中&#xff0c; 顺序关系主要分为以下三类&#xff1a;时间顺序&#xff1a; 事件在时间轴上发生的先后关系。 无限时刻集组成有向时间轴&#xff0c; 时间顺序是通过时刻的顺序体现的。 因果顺序&#xff1a; 如果事件e1是事件e2发生的原因&#xf…

Android Studio代码联想不区分大小写的方法

Android Studio默认的代码联想是要区分大小写的 例如Bitmap&#xff0c;输入bit后并不会有提示 为了让其不区分大小写&#xff0c;可以在 File --> Setting 中进行设置 依次选择 Editor --> General --> Code Completion &#xff0c;将 Match case取消勾选即可 这个…

半小时实现GPT纯血鸿蒙版

仅需半小时&#xff0c;即可实现纯血鸿蒙版本的ChatGPT&#xff01; 废话少说&#xff0c;先看效果图&#xff1a; 如上图所示&#xff0c;这个小Demo实现了AI智能问答。靠右加粗的文本是用户点击底部提交按钮后出现的&#xff1b;后面靠左对齐的普通文本是来自AI的回答内容。当…

Spark原理——Shuffle 过程

Shuffle 过程 Shuffle过程的组件结构 从整体视角上来看, Shuffle 发生在两个 Stage 之间, 一个 Stage 把数据计算好, 整理好, 等待另外一个 Stage 来拉取 放大视角, 会发现, 其实 Shuffle 发生在 Task 之间, 一个 Task 把数据整理好, 等待 Reducer 端的 Task 来拉取 如果更细…

【数据集处理】FFHQ如何进行人脸对齐,Aligned and cropped images at 1024×1024

什么是人脸对齐&#xff1f; 人脸对齐是一种图像处理技术&#xff0c;旨在将图像中的人脸部分对齐到一个标准位置或形状。在许多情况下&#xff0c;这通常涉及将眼睛、鼻子和嘴巴等关键点对齐到特定的位置。通过这种方式&#xff0c;所有的人脸图像可以有一个一致的方向和尺寸…

josef约瑟 中间继电器 HJDZ-E440额定电压:AC220V 卡轨安装

HJDZ-静态中间继电器 系列型号&#xff1a; HJDZ-A200静态中间继电器&#xff1b;HJDZ-A110静态中间继电器&#xff1b; HJDZ-A002静态中间继电器&#xff1b;HJDZ-A004静态中间继电器&#xff1b; HJDZ-E112静态中间继电器&#xff1b;HJDZ-E112L静态中间继电器&#xff1…

opencv(C++)基础用法

文章目录 前言一、opencv (C)图片基本操作1.1 读取图片并显示1.2 颜色转换1.3 图像filtering1.4 形状调整1.5 绘制 二、读取视频文件并显示三、RTSP 视频流四. 人脸检测总结 前言 学习笔记 一、opencv (C)图片基本操作 1.1 读取图片并显示 #include "opencv2/opencv.hp…

操作系统-操作系统的概念和功能

文章目录 大家熟悉的操作系统总览操作系统的概念&#xff08;定义&#xff09;操作系统的功能和目标-作为系统资源的管理者操作系统的功能和目标-向上层提供方便易用的服务图形化用户接口联机命令接口脱机命令接口程序接口小结 操作系统的功能和目标-作为最解决硬件的层次小结 …

Go-安装与基础语法

TOC 1. Go 安装与环境变量 1.1 下载 需要从Go语言的官方网站下载适合你操作系统的Go语言安装包。Go语言支持多种操作系统&#xff0c;包括Windows、Linux和Mac OS。 对于Windows用户&#xff0c;下载.msi文件&#xff0c;然后双击该文件&#xff0c;按照提示进行安装即可。…

【电路电子学】7天速通攻略+笔记

7天是 看视频记笔记刷题的总时长&#xff0c;时间紧迫的同学可以看情况进行缩减。个人认为做题&#xff0c;尤其是解析齐全的题最重要&#xff01; 我校所用教材 《电路与电子学基础》唐胜安 复习总流程 所用材料&#xff08;都可自行找到免费资源&#xff09; 视频知识点讲…