操作系统面经-多线程同步的四种方式

对于多线程程序来说,同步是指在一定的时间内只允许某一个线程来访问某个资源。而在此时间内,不允许其他的线程访问该资源。可以通过互斥锁(Mutex)、条件变量(condition variable)、读写锁(reader-writer lock)、信号量(semaphore)来同步资源。

  1. 互斥锁(Mutex)

    互斥量是最简单的同步机制,即互斥锁。多个进程(线程)均可以访问到一个互斥量,通过对互斥量加锁,从而来保护一个临界区,防止其它进程(线程)同时进入临界区,保护临界资源互斥访问。

    互斥锁需要满足三个条件:

    • 互斥 不同线程的临界区没有重叠
    • 无死锁 如果一个线程正在尝试获得一个锁,那么总会成功地获得这个锁。若线程A调用lock()但是无法获得锁,则一定存在其他线程正在无穷次地执行临界区。
    • 无饥饿 每一个试图获得锁的线程最终都能成功。
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>void *function(void *arg);
    pthread_mutex_t mutex;
    int counter = 0;
    int main(int argc, char *argv[])
    {int rc1,rc2;char *str1="hello";char *str2="world";pthread_t thread1,thread2;pthread_mutex_init(&mutex,NULL);if((rc1 = pthread_create(&thread1,NULL,function,str1))){fprintf(stdout,"thread 1 create failed: %d\n",rc1);}if(rc2=pthread_create(&thread2,NULL,function,str2)){fprintf(stdout,"thread 2 create failed: %d\n",rc2);}pthread_join(thread1,NULL);pthread_join(thread2,NULL);return 0;
    }void *function(void *arg)
    {char *m;m = (char *)arg;pthread_mutex_lock(&mutex);while(*m != '\0'){printf("%c",*m);fflush(stdout);m++;sleep(1);}printf("\n");pthread_mutex_unlock(&mutex);
    }
    
  2. 条件变量(condition variable)

    生产者消费者问题:每次生产一个商品,发一个信号,告诉消费者“我生产商品了,快来消费”,消费者拿到生产者的条件变量后每次消费两个商品,然后发出信号“我消费了商品,你可以生产了”--_--(发的这个信号是一个条件变量,通过发送这个信号可以唤醒阻塞的线程,收到信号后,不满足需求也会继续阻塞)

    为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;条件变量是线程的另一种同步机制,它和互斥量是一起使用的。互斥量的目的就是为了加锁,而条件变量的结合,使得线程能够以等待的状态来迎接特定的条件发生,而不需要频繁查询锁。

    #include <pthread.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>// 定义条件变量cond_productpthread_cond_t cond_product;// 定义条件变量cond_consumepthread_cond_t cond_consume;// 定义线程互斥锁lockpthread_mutex_t lock;//初始化函数
    void init_work(void)
    {
    // 条件变量的初始化pthread_cond_init(&cond_product, NULL);pthread_cond_init(&cond_consume, NULL);// 线程锁的初始化pthread_mutex_init(&lock, NULL);
    }//生产线程,每次生产一个产品
    void* handle_product(void *arg){int i;int* product = NULL;product = (int *) arg;for(i=1; i<50; i++) {pthread_mutex_lock(&lock); //生产进程上锁,是消费进程无法改变商品个数if (*product >= 4) {
    // 仓库已满,应该阻塞式等待printf("\033[43m仓库已满, 暂停生产...\033[0m\n");pthread_cond_wait(&cond_product, &lock);}printf("生产中....\n");sleep(2);printf("生产成功....\n");*product+=1;pthread_cond_signal(&cond_consume);//发出信号,条件已经满足printf("\033[32m生产一个产品,生产%d次,仓库中剩余%d个\033[0m\n",i,*product);printf ("发信号--->生产成功\n");pthread_mutex_unlock(&lock);//生产进程解锁usleep(50000);}return NULL;
    }//消费线程,每次消费两个产品,消费6次间歇
    void* handle_consume(void *arg){int i;int* product = NULL;product = (int *)arg;for (i=1; i<50; i++) {pthread_mutex_lock(&lock);if (*product <= 1) //消费线程每次消费2个,故总产品数量小于1即阻塞{
    /* 阻塞式等待 */printf("\033[43m缺货中,请等待...\033[0m\n");pthread_cond_wait(&cond_consume, &lock);}            /* 消费产品,每次从仓库取出两个产品 */printf("消费中...\n");sleep(2);*product-=2;printf("消费完成...\n");printf("\033[31m消费两个产品,共消费%d次,仓库剩余%d个\033[0m\n",i,*product);pthread_cond_signal(&cond_product);printf ("发信号---> 已消费\n");pthread_mutex_unlock(&lock);usleep(30000);if (i%6 == 0){ //消费间歇sleep(9);}}return NULL;
    }int main()
    {pthread_t th_product, th_consume; //定义线程号int ret;int intrinsic = 3;
    //初始化所有变量init_work();
    //创建进程并传递相关参数ret = pthread_create(&th_product, 0, handle_product, &intrinsic);if (ret != 0) {perror("创建生产线程失败\n");exit(1);}ret = pthread_create(&th_consume, 0, handle_consume, &intrinsic);if (ret != 0) {perror("创建消费线程失败\n");exit(1);}pthread_join(th_product, 0);//回收生产线程pthread_join(th_consume, 0);//回收消费线程return 0;
    }
    
  3. 读写锁(reader-writer lock)

    前面介绍的互斥量加锁要么是锁状态,要么就是不加锁状态。而且只有一次只有一个线程可以对其加锁。这样的目的是为了防止变量被不同的线程修改。但是如果有线程只是想读而不会去写的话,这有不会导致变量被修改。但是如果是互斥量加锁,则读写都没有办法。这种场景不能使用互斥量,必须使用读写锁。

    读写锁可以有3种状态:

    1. 读模式下加锁状态

    2. 写模式下加锁状态

    3. 不加锁状态

    一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权。但是任何希望以写模式对此锁进行加锁的线程都会阻塞。直到所有的线程释放它们的读锁为止。

    读写锁非常适合于对数据结构读的次数大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为一次只有一个线程可以在写模式下拥有这个锁。

    读写锁也叫做共享互斥锁。当读写锁是读模式锁住的,就可以说是以共享模式锁住的。当它是写模式锁住的时候,就可以说成是以互斥模式锁住的。

    #include <pthread.h>

    Int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

    Int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

    读写锁通过调用pthread_rwlock_init进行初始化。在释放读写锁占有的内存之前,需要调用pthread_rwlock_destroy做清理工作。如果pthread_rwlock_init为读写锁分配了资源,pthread_rwlock_destroy将释放这些资源。如果在调用pthread_rwlock_destroy之前就释放了读写锁占用的内存空间。那么分配给这个锁的资源就会丢失。

    要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock,要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock。不管以何种方式锁住读写锁。都可以调用pthread_rwlock_unlock进行解锁。

    Int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    Int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

    Int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    #include <pthread.h>
    #include <stdio.h>pthread_rwlock_t rwlock;int data=1;void readerA(){while(1){pthread_rwlock_rdlock(&rwlock);printf("A读者读出:%d\n",data);pthread_rwlock_unlock(&rwlock);Sleep(1000);}}void writerB(){while(1){pthread_rwlock_wrlock(&rwlock);data++;printf("B作者写入:%d\n",data);pthread_rwlock_unlock(&rwlock);Sleep(1000);}}int main(){pthread_t t1;pthread_t t2;pthread_rwlock_init(&rwlock,NULL);pthread_create(&t1,NULL,readerA,NULL);pthread_create(&t2,NULL,writerB,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_rwlock_destroy(&rwlock);return 0;}
    
  4. 信号量(semaphore)

    在生产者消费者模型中,对任务数量的记录就可以使用信号量来做。可以理解为带计数的条件变量。当信号量的值小于0时,工作进程或者线程就会阻塞,等待物品到来。当生产者生产一个物品,会将信号量值加1操作。 这是会唤醒在信号量上阻塞的进程或者线程,它们去争抢物品。

    这里用一个读写文件作为例子:

    首先需要用sem_init(); 初始化sem_t型变量,并设置初始信号量。比如设置为1.

    每次调用sem_wait(sem_t *); 信号量减一,当调用sem_post(sem_t *); 信号量加一。

    当信号量为0时,sem_wait(); 函数阻塞,等待信号量 >0 时,才进行。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct{sem_t *lock;int num;
}STRUCT;
void test(void * obj)
{STRUCT *point = (STRUCT *)obj;sem_t *semlock = point->lock;sem_wait(semlock);FILE *f = fopen("test.txt","a");if(f==NULL)printf("fopen is wrong\n");printf("sem_wait %d\n",point->num);int j=0;for(j=0;j<30;j++)fprintf(f,"%c111111111111\n",'a'+point->num);fclose(f);sem_post(semlock);return;
}int main()
{pthread_t pid[20];  //  pthread_t pid;int ret,i=0;STRUCT obj[13];sem_t semlock;if(sem_init(&semlock,0,1)!=0)    // 此处初始信号量设为1. 第二个参数为0表示不应用于其他进程。printf("sem_init is wrong\n");for(i=0;i<10;i++){obj[i].num = i;obj[i].lock = &semlock;ret = pthread_create(&pid[i],NULL,(void *)test,&obj[i]);if(ret!=0){printf("create thread wrong %d!!\n",i);return 0;}}for(i=0;i<10;i++)pthread_join(pid[i],NULL);  // 等待其他线程结束,如果没有这里,主线程先结束,会释放pid[]及obj[],则出现BUG。 return 0;
}

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

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

相关文章

XUbuntu22.04之跨平台日历工具(二百二十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

ChatGPT编程秘籍:轻松学习人工智能写作

ChatGPT无限次数:点击直达 html ChatGPT编程秘籍&#xff1a;轻松学习人工智能写作 引言 人工智能技术的发展给写作领域带来了革命性的变化&#xff0c;其中自然语言生成&#xff08;NLG&#xff09;技术的发展尤为引人注目。ChatGPT是一款基于大规模预训练模型的人工智能…

数据结构与算法-排序算法

1.顺序查找 def linear_search(iters, val):for i, v in enumerate(iters):if v val:return ireturn 2.二分查找 # 升序的二分查找 def binary_search(iters, val):left 0right len(iters)-1while left < right:mid (left right) // 2if iters[mid] val:return mid…

300.【华为OD机试】跳房子I(时间字符串排序—JavaPythonC++JS实现)

本文收录于专栏:算法之翼 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目二.解题思路三.题解代码Python题解代码JAVA题解代码C/C++题解代码JS题解代码四.代码讲解(Ja…

WSL2 设置桥接模式

文章目录 一、前言二、准备阶段三、环境配置3.1 Type-V管理器环境配置3.2 新增.wslconfig 文件 四、遇到的问题以及解决方案 一、前言 ​ 使用 wsl 的过程中&#xff0c;会出现 WSL 的IP地址 找不到&#xff0c;或者无法和计算机通讯&#xff0c;搞 嵌入式 的话&#xff0c;还…

网络原理(7)——以太网数据帧和DNS协议(数据链路层和应用层)

目录 一、以太网数据帧&#xff08;数据链路层&#xff09; 二、DNS协议(域名解析系统&#xff0c;应用层协议) 一、以太网数据帧&#xff08;数据链路层&#xff09; 以太网横跨了数据链路层和物理层&#xff0c;这里只做简单介绍&#xff0c;因为普通程序员用不到这一块&am…

数据结构——顺序表(C语言版)

顺序表是数据结构中最基本的一种线性表&#xff0c;它以一段连续的存储空间来存储数据元素&#xff0c;元素之间的顺序由它们在内存中的位置来决定。在C语言中&#xff0c;我们通常使用数组来实现顺序表。 目录 顺序表的结构定义 顺序表的基本操作 应用实例 顺序表的结构定义…

Docker与K8S实战系列教程--靠谱、易学

在招聘网站上&#xff0c;Docker和K8S已经成为频繁出现的技能&#xff1a; 对于Java高级开发、架构师、技术总监&#xff0c;Docker和K8S是必备技能。对于Java初级、中级开发&#xff0c;Docker和K8S是简历里的加分项。 为什么Docker和K8S这么重要&#xff1f; 因为Docker和…

AUTOSAR汽车电子嵌入式编程精讲300篇-基于CAN总线的新能源汽车综合仪表系统(续)

目录 3 新能源汽车仪表硬件系统设计 3.1总体设计 3.2主要元器件选型 3.2.1主控芯片选型

浅谈linux下的进程地址空间(虚拟地址/线性地址)

目录 什么是地址空间 - 虚拟地址空间 地址空间是如何设计的 为什么要有地址空间 什么是地址空间&#xff1f; 示例&#xff1a; 运行之后发现&#xff1a;同一个变量&#xff0c;同一个地址&#xff0c;在运行一段时间后&#xff0c;竟然会在同一时间出现两个不同的值&…

阿里云短信平台收费价格表,短信服务优惠0.032元一条

2024年阿里云短信服务优惠价格表&#xff0c;阿里云短信多少钱一条&#xff1f;低至0.01元一条&#xff0c;200条短信仅需2元&#xff0c;最高可领2000条短信免费赠送&#xff0c;短信套餐包2000条、5000条、5万条等均有活动&#xff0c;阿里云百科aliyunbaike.com整理2024年最…

数字乡村战略实施:科技引领农村经济社会全面发展

随着信息技术的快速发展&#xff0c;数字化已经成为推动经济社会发展的重要力量。在乡村振兴战略的大背景下&#xff0c;数字乡村战略的实施成为了引领农村经济社会全面发展的关键。本文将从数字乡村战略的内涵、实施现状、面临挑战及未来展望等方面&#xff0c;探讨科技如何引…

MySQL数据库备份策略与实践详解

目录 引言 一、MySQL数据库备份的重要性 &#xff08;一&#xff09;数据丢失的原因 &#xff08;二&#xff09;数据丢失的后果 二、MySQL备份类型 &#xff08;一&#xff09;根据数据库状态 &#xff08;二&#xff09;根据数据的完整性 &#xff08;三&#xff09;…

线程的状态:操作系统层面和JVM层面

在操作系统层面&#xff0c;线程有五种状态 初始状态&#xff1a;线程被创建&#xff0c;操作系统为其分配资源。 可运行状态(就绪状态)&#xff1a;线程被创建完成&#xff0c;进入就绪队列&#xff0c;参与CPU执行权的争夺。或因为一些原因&#xff0c;从阻塞状态唤醒的线程…

【ES6】Set和Map数据结构

目录 Set 用法 Set属性和方法 遍历 Map 用法 属性和方法 遍历 Set 用法 ES6 提供了新的数据结构 Set。它类似于数组&#xff0c;但是成员的值都是唯一的&#xff0c;没有重复的值。 Set本身是一个构造函数&#xff0c;用来生成 Set 数据结构。add()方法向 Set 结构加入…

CCF-CSP认证考试 202212-2 训练计划 100分题解

更多 CSP 认证考试题目题解可以前往&#xff1a;CSP-CCF 认证考试真题题解 原题链接&#xff1a; 202212-2 训练计划 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题背景 西西艾弗岛荒野求生大赛还有 n n n 天开幕&#xff01; 问题描述 为了在大赛中取得…

【Linux】进程的基本概念(进程控制块,ps命令,top命令查看进程)

目录 01.进程的基本概念 程序与进程 进程的属性 02.进程控制块&#xff08;PCB&#xff09; task_struct的内容分类 组织进程 03.查看进程 ps命令 top指令 在计算机科学领域&#xff0c;进程是一项关键概念&#xff0c;它是程序执行的一个实例&#xff0c;是操作系统的…

【逆向】利用Objection实现移动应用抓取https流量

那女孩对我说 说我保护她的梦 说这个世界 对她这样的不多 她渐渐忘了我 但是她并不晓得 遍体鳞伤的我 一天也没再爱过 &#x1f3b5; 黄义达《那女孩对我说》 在移动应用开发和安全测试中&#xff0c;抓包是一项重要的技能&#xff0c;它可以帮助开发者和…

Mybatis的核心配置文件

MyBatis的全局配置文件mybatis-config.xml&#xff0c;配置内容如下&#xff1a; properties&#xff08;属性&#xff09;settings&#xff08;全局配置参数&#xff09;typeAliases&#xff08;类型别名&#xff09;typeHandlers&#xff08;类型处理器&#xff09;objectFa…

面试知识汇总——垃圾回收器(分代收集算法)

分代收集算法 根据对象的存活周期&#xff0c;把内存分成多个区域&#xff0c;不同区域使用不同的回收算法回收对象。 对象在创建的时候&#xff0c;会先存放到伊甸园。当伊甸园满了之后&#xff0c;就会触发垃圾回收。 这个回收的过程是&#xff1a;把伊甸园中的对象拷贝到F…