Linux系统编程(十二)线程同步、锁、条件变量、信号量

线程同步:

协同步调,对公共区域数据按序访问。防止数据混乱,产生与时间有关的错误。

数据混乱的原因

数据混乱的原因

一、互斥锁/互斥量mutex

线程同步和锁

1. 建议锁(协同锁):

公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但,锁本身不具备强制性。

建议锁

2. 互斥锁的使用步骤:

互斥锁的使用步骤

初始化互斥量:pthread_mutex_t mutex;1. pthread_mutex_init(&mutex, NULL);   			动态初始化。2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;	静态初始化。

restrict关键字:

用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,只能由本指针完成。

3. 使用锁的注意事项:

尽量保证锁的粒度, 越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)互斥锁,本质是结构体。 可以看成整数。 初始化时值为 1。(pthread_mutex_init() 函数调用成功。)lock加锁: --操作, 阻塞线程。unlock解锁: ++操作, 换醒阻塞在锁上的线程。try锁:尝试加锁,成功--。失败直接返回,同时设置错误号 EBUSY,不阻塞。

4. 死锁:

是使用锁不恰当导致的现象:1. 线程视图对同一个互斥量A加锁两次。2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁。

死锁

示例:访问共享数据(stdout)
//pthrd_shared.c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>pthread_mutex_t mutex;void *tfn(void *arg)
{srand(time(NULL));while (1) {pthread_mutex_lock(&mutex);printf("hello ");sleep(rand() % 3);	/*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/printf("world\n");pthread_mutex_unlock(&mutex);sleep(rand() % 3);//保证锁的粒度越小越好,(访问结束立即解锁)}return NULL;
}int main(void)
{pthread_t tid;srand(time(NULL));pthread_mutex_init(&mutex, NULL);pthread_create(&tid, NULL, tfn, NULL);while (1) {pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");pthread_mutex_unlock(&mutex);sleep(rand() % 3);}pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);return 0;
}

二、读写锁:

  • 锁只有一把。以读方式给数据加锁——读锁。以写方式给数据加锁——写锁。

  • 读共享,写独占。

  • 写锁优先级高。

  • 相较于互斥量而言,当读线程多的时候,提高访问效率

读写锁原理

读写锁特性

pthread_rwlock_t  rwlock;类型pthread_rwlock_init(&rwlock, NULL);pthread_rwlock_rdlock(&rwlock);		pthread_rwlock_wrlock(&rwlock);		pthread_rwlock_tryrdlock(&rwlock);		pthread_rwlock_trywrlock(&rwlock);		pthread_rwlock_unlock(&rwlock);pthread_rwlock_destroy(&rwlock);

示例:3个线程不定时 “写” 全局资源,5个线程不定时 “读” 同一全局资源

//rwlock.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int counter;                          //全局资源
pthread_rwlock_t rwlock;void *th_write(void *arg)
{int t;int i = (int)arg;while (1) {t = counter;                    // 保存写之前的值usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);pthread_rwlock_unlock(&rwlock);usleep(9000);               // 给 r 锁提供机会}return NULL;
}void *th_read(void *arg)
{int i = (int)arg;while (1) {pthread_rwlock_rdlock(&rwlock);printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);pthread_rwlock_unlock(&rwlock);usleep(2000);                // 给写锁提供机会}return NULL;
}int main(void)
{int i;pthread_t tid[8];pthread_rwlock_init(&rwlock, NULL);for (i = 0; i < 3; i++)pthread_create(&tid[i], NULL, th_write, (void *)i);for (i = 0; i < 5; i++)pthread_create(&tid[i+3], NULL, th_read, (void *)i);for (i = 0; i < 8; i++)pthread_join(tid[i], NULL);pthread_rwlock_destroy(&rwlock);            //释放读写琐return 0;
}

三、条件变量:

本身不是锁! 但是通常结合锁来使用。 mutex

pthread_cond_wait函数

pthread_cond_t cond;//类型初始化条件变量:1. pthread_cond_init(&cond, NULL);   			动态初始化。2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;	静态初始化。阻塞等待条件:3. pthread_cond_wait(&cond, &mutex);作用:	1) 阻塞等待条件变量满足2) 解锁已经加锁成功的信号量 (相当于 pthread_mutex_unlock(&mutex))注:1和2两步为一个原子操作3)  当条件满足,函数返回时,重新加锁信号量 (相当于, pthread_mutex_lock(&mutex);)4. pthread_cond_timedwait():可设置超时5. pthread_cond_signal(): 唤醒阻塞在条件变量上的 (至少)一个线程。6. pthread_cond_broadcast(): 唤醒阻塞在条件变量上的 所有线程。7. pthread_cond_destroy(&cond):销毁条件变量

示例:使用条件变量完成生产者、多个消费者模型

条件变量-生产者/消费者模型

//借助条件变量模拟 生产者-消费者问题
//pthread_cond_produce_consumer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void err_thread(int ret, char *str)
{if (ret != 0) {fprintf(stderr, "%s:%s\n", str, strerror(ret));pthread_exit(NULL);}
}struct msg {int num;struct msg *next;
};struct msg *head;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定义/初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量void *produser(void *arg)
{while (1) {struct msg *mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`printf("--produce %d\n", mp->num);pthread_mutex_lock(&mutex);                         // 加锁 互斥量mp->next = head;                                    // 写公共区域head = mp;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量pthread_cond_signal(&has_data);                     // 唤醒阻塞在条件变量 has_data上的线程.sleep(rand() % 3);}return NULL;
}void *consumer(void *arg)
{while (1) {struct msg *mp;pthread_mutex_lock(&mutex);                         // 加锁 互斥量while (head == NULL) {															// 注:多个消费者,这里需要使用while,不能使用ifpthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁}                                                   // pthread_cond_wait 返回时, 重新加锁 mutexmp = head;																					//读公共区域head = mp->next;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量printf("---------consumer id: %lu :%d\n", pthread_self(), mp->num);free(mp);sleep(rand()%3);}return NULL;
}int main(int argc, char *argv[])
{int ret;pthread_t pid, cid;srand(time(NULL));ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者if (ret != 0) err_thread(ret, "pthread_create produser error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");pthread_join(pid, NULL);pthread_join(cid, NULL);return 0;
}

四、信号量:

  • 应用于线程、进程间同步(既能保证同步,数据不混乱,又能提高线程并发)。

  • 相当于 初始化值为 N 的互斥量。 N值,表示可以同时访问共享数据区的线程数。

信号量函数

sem_t sem;	定义类型。int sem_init(sem_t *sem, int pshared, unsigned int value);参数:sem: 信号量 pshared:	0: 用于线程间同步1: 用于进程间同步value:N值。(指定同时访问的线程数)sem_destroy();sem_wait();		一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。 (对比 pthread_mutex_lock)sem_post();		一次调用,做一次++ 操作. 当信号量的值为 N 时, 再次 ++ 就会阻塞。(对比 pthread_mutex_unlock)sem_timewait(sem_t *sem, const struct timespec* abs_timeout);//abs_timeout 采用的是绝对时间。定时1秒:time_t cur = time(NULL);获取当前时间(相对时间)struct timespec t;定义timespec结构体变量tt.tv_sec = cur + 1;定时1秒t.tv_nsec = t.tv_sec+100;sem_timedwait(&sem, &t);传参

示例:使用信号量实现生产者、消费者模型

信号量-生产者消费者模型

//sem_product_consumer.c
/*信号量实现 生产者 消费者问题*/#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>#define NUM 5               int queue[NUM];                                     //全局数组实现环形队列
sem_t blank_number, product_number;                 //空格子信号量, 产品信号量void *producer(void *arg)
{int i = 0;while (1) {sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待queue[i] = rand() % 1000 + 1;               //生产一个产品printf("----Produce---%d\n", queue[i]);        sem_post(&product_number);                  //将产品数++i = (i+1) % NUM;                            //借助下标实现环形sleep(rand()%1);}
}void *consumer(void *arg)
{int i = 0;while (1) {sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待printf("-Consume---%d\n", queue[i]);queue[i] = 0;                               //消费一个产品 sem_post(&blank_number);                    //消费掉以后,将空格子数++i = (i+1) % NUM;sleep(rand()%3);}
}int main(int argc, char *argv[])
{pthread_t pid, cid;sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享 -- 0sem_init(&product_number, 0, 0);                //产品数为0pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);pthread_join(cid, NULL);sem_destroy(&blank_number);sem_destroy(&product_number);return 0;
}

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (277)-- 算法导论20.3 4题

四、如果调用 vEB-TREE-INSERT 来插入一个已包含在 vEB 树中的元素&#xff0c;会出现什么情况&#xff1f;如果调用 vEB-TREE-DELETE 来删除一个不包含在 vEB 树中的元素&#xff0c;会出现什么情况&#xff1f;解释这些函数为什么有相应的运行状况&#xff1f;怎样修改 vEB 树…

vs - vs2015编译gtest-v1.12.1

文章目录 vs - vs2015编译gtest-v1.12.1概述点评笔记将工程迁出到本地后&#xff0c;如果已经编译过工程&#xff0c;将工程Revert, Clean up 干净。编译用的CMake, 优先用VS2019自带的打开VS2015X64本地命令行编译gtest工程测试安装自己写个测试工程&#xff0c;看看编译出来的…

[vulnhub]Lin.Security主机Linux提权

Hash Crack(Hash cat) boblinsecurity:~$ cat /etc/passwd $ echo "AzER3pBZh6WZE">hash 检查哈希类型: $ hash-identifier AzER3pBZh6WZE $ hashcat -m 1500 -a 0 hash /usr/share/wordlists/rockyou.txt --force username:insecurity password:AzER3pBZh6WZE…

深度学习简单概述

概述 理论上来说&#xff0c;参数越多的模型复杂度越高、容量越大&#xff0c;这意味着它能完成更复杂的学习任务。但复杂模型的训练效率低&#xff0c;易陷入过拟合。随着云计算、大数据时代的到来&#xff0c;计算能力的大幅提高可以缓解训练的低效性&#xff0c;训练数据的…

Unity2D游戏制作入门 | 09(之人物动画制作)

上期链接&#xff1a;Unity2D游戏制作入门 | 08-CSDN博客 人物走路动画逻辑补充&#xff08;该帖没有的内容&#xff0c;我给补充了请先看完这帖&#xff0c;再去看补充&#xff09;&#xff1a;人物按下shifit走路动画设定09&#xff08;第九期先行补充&#xff09; 上期我们…

【30天精通Prometheus:一站式监控实战指南】第18天:apache_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

57.Semaphore信号量

用来限制能同时访问共享资源的线程上限。只是适合限制单机线程数量。 Slf4j public class SemaphoreDemo {public static void main(String[] args) {Semaphore semaphore new Semaphore(3);for (int i 0; i < 10; i) {new Thread(() -> {try {semaphore.acquire();//…

【Mac】Alfred 5 for Mac(苹果效率提升工具)v5.5软件介绍及安装教程

软件介绍 Alfred 是适用于 Mac 操作系统的流行生产力应用程序。它旨在帮助用户在 Mac 电脑上更高效地启动应用程序、搜索文件和文件夹以及执行各种任务。借助 Alfred&#xff0c;用户可以创建自定义键盘快捷方式、设置自定义工作流程并使用热键访问功能。 Alfred for Mac 的一…

电影制作中的版本控制:Perforce Helix Core帮助某电影短片避免灾难性文件损坏,简化艺术资产管理

Zubaida Nila是来自马来西亚的一名视觉特效师和虚拟制作研究员&#xff0c;她参加了Epic Games的一个为期六周的虚拟培训和指导项目——女性创作者计划。该计划提供了虚幻引擎工作流程的实践经验以及其他课程。Zubaida希望从中获得更多关于虚幻引擎的灯光、后期处理和特效技能方…

DS:堆的结构与实现

欢迎来到Harper.Lee的学习世界&#xff01;博主主页传送门&#xff1a;Harper.Lee的博客主页想要一起进步的uu可以来后台找我哦&#xff01; 一、堆的概念与结构 1.1 堆的概念 堆&#xff08;Heap&#xff09;是完全二叉树中的一种&#xff0c;分为大根堆和小根堆。 特点&#…

解决PyQt5中柱状图上显示的数值为带e的科学计数法

PyQt5生成柱状图的代码参考&#xff1a;PyQt5 QtChart-柱状图 参照上述文章&#xff0c;生成柱状图后&#xff0c;数值较大或较小情况下会导致柱状图上显示数值为带e的科学计数法&#xff0c;这样会影响数值的识别&#xff1a; 经过分析QBarSet方法得到解决方法&#xff1a;需…

HCIA--NAT地址转换(复习)

先交换后路由&#xff1a; 1&#xff1a;在交换机上创建vlan&#xff0c;进入接口划分vlan&#xff0c;接着在交换机连接路由器的接口上建立trunk干道 2&#xff1a;在路由器上&#xff0c;先配置物理接口IP&#xff0c;接着在路由器上创建两个子接口&#xff0c;将建立的vla…

使用React和GraphQL进行CRUD:完整教程与示例

在本教程中&#xff0c;我们将向您展示如何使用GraphQL和React实现简单的端到端CRUD操作。我们将介绍使用React Hooks读取和修改数据的简单示例。我们还将演示如何使用Apollo Client实现身份验证、错误处理、缓存和乐观UI。 什么是React&#xff1f; React是一个用于构建用户…

【C语言】轻松拿捏-联合体

谢谢观看&#xff01;希望以下内容帮助到了你&#xff0c;对你起到作用的话&#xff0c;可以一键三连加关注&#xff01;你们的支持是我更新地动力。 因作者水平有限&#xff0c;有错误还请指出&#xff0c;多多包涵&#xff0c;谢谢&#xff01; 联合体 一、联合体类型的声明二…

DDMA信号处理以及数据处理的流程---随机目标生成

Hello&#xff0c;大家好&#xff0c;我是Xiaojie&#xff0c;好久不见&#xff0c;欢迎大家能够和Xiaojie一起学习毫米波雷达知识&#xff0c;Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程&#xff0c;本系列文章将从目标生成、信号仿真、测距、测速、cfar…

ToonCrafter - AI 生成动画越来越简单了,两张照片生成动画视频 本地一键整合包

动画制作对很多人来说应该都是一项非常专业且复杂的工作&#xff0c;需要学习专门的知识&#xff0c;掌握特定的工具&#xff0c;并且投入大量的时间精力才能得到成果&#xff0c;不过最近出现的一款 AI 动画制作工具 ToonCrafter 则有希望改变这一现状。它只需 2 张图像就生成…

[个人感悟] Java基础问题应该考察哪些问题?

前言 “一切代码无非是数据结构和算法流程的结合体.” 忘了最初是在何处看见这句话了, 这句话, 对于Java基础的考察也是一样. 正如这句话所说, 我们对于基础的考察主要考察, 数据结构, 集合类型结构, 异常类型, 已经代码的调用和语法关键字. 其中数据结构和集合类型结构是重点…

fl studio怎么设置中文及 2024年最新fl studio选购指南

FL Studio让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制。zol提供FL Studio中文版下载。 FL Studio中文版下载软件简介 FL Studio 让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混…

开发没有尽头,尽力既是完美

最近遇到了一些难题&#xff0c;开发系统总有一些地方没有考虑周全&#xff0c;偏偏用户使用的时候“完美复现”了这个隐藏的Bug...... 讲道理创业一年之久为了生存&#xff0c;我一直都有在做复盘&#xff0c;复盘的核心就是&#xff1a;如何提升营收、把控开发质量&#xff0…

Linux基础 (十五):TCP 协议特点和UDP协议

上一节&#xff0c;我们学习了TCP协议的服务器-客户端的编程流程以及对中间的过程进行了详细的讨论&#xff0c;那么&#xff0c;这一节&#xff0c;我们对于TCP协议的特点进行进一步的分析&#xff0c;这也是面试的重点和难点。 目录 一、TCP 协议特点 1.1 连接的建立与断…