多线程与信号量简介

信号量与 PV 操作

计算机中信号量的本质是整数,数值表示可用的资源数量

P 操作 (Passeren => 通过, 原子操作)

  • 若信号量 == 0,当前任务阻塞 (进入信号量等待队列)
  • 若信号量 > 0,则:将信号量数值减一,当前任务继续执行

V 操作 (Vrijgeven => 释放, 原子操作)

  • 将信号量数值加一
  • 若 信号量 > 0,则:唤醒阻塞的其它任务,当前任务继续执行

信号量与 PV 操作注意事项

程序中的 PV 操作必须成对出现 (P 操作 => 临界区 => V 操作)

信号量初始值一般为 1 (初始值与相应资源数量相关)

信号量也看做特殊的互斥量 (信号量初始值为 1 时,退化为互斥量)

若 信号量 == S && S > 0,则:可进行 P 操作并且不阻塞的次数为 S

信号量模拟用法示例

Linux 中的信号量

下面的程序输出什么?为什么?

test1.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

void* customer_thread(void* arg)
{   
    printf("thread begin %ld\n", pthread_self());
    
    sem_wait(arg);
    
    printf("thread end %ld\n", pthread_self());
        
    return NULL;
}

int main()
{
    pthread_t t = {0};
    sem_t sem = {0};
    int i = 0;
    int v = 0;

    sem_init (&sem, PTHREAD_PROCESS_PRIVATE, 1);
    
    sem_getvalue(&sem, &v);
    
    printf("sem = %d\n", v);

    for(i=0; i<5; i++)
    {
        pthread_create(&t, NULL, customer_thread, &sem);
    }
    
    sleep(5);
    
    sem_getvalue(&sem, &v);
    
    printf("sem = %d\n", v);

    printf("End!\n");
  
    return 0;
}

第 28 行,初始化信号量,将信号量的初始值设置为 1,那么这里的信号量等同与互斥锁

第 36 行,主线程创建了 5 个子线程,子线程通过 sem_wait() 去获取信号量

第 41 行,主线程通过 sem_getvalue() 来获取当前信号量的值

由于信号量的初始值为 1,所以只能有一个子线程获取到信号量,其它的线程来获取信号量时,发现信号量的值为 0,就会阻塞等待信号量被释放

程序运行结果如下图所示:

只有一个子线程获取到了信号量,最后信号量的值为 0

生产消费者问题示例

信号量初体验

我们使用信号量来解决生产者消费者问题

test3.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

typedef struct
{
    sem_t r;
    sem_t w;
} Sem;

void* customer_thread(void* arg)
{   
    Sem* s = arg;  
    
    sleep(1);
    
    while( 1 )
    {
        sem_wait(&s->r);
        
        printf("%s : get\n", __FUNCTION__);
        
        sem_post(&s->w);
    }
        
    return NULL;
}

int main()
{
    pthread_t t = {0};
    Sem sem = {0};
    
    sem_init (&sem.r, PTHREAD_PROCESS_PRIVATE, 0);
    sem_init (&sem.w, PTHREAD_PROCESS_PRIVATE, 1);

    pthread_create(&t, NULL, customer_thread, &sem);
    
    printf("Hello World!\n");
    
    while( 1 )
    {
        sem_wait(&sem.w);
        
        printf("%s : set\n", __FUNCTION__);
        
        sem_post(&sem.r);
        
        sleep(3);
    }

    printf("End!\n");
  
    return 0;
}

该程序中使用的两个信号量,信号量 w 和信号量 r,信号量 w 的初始值为 1,信号量 r 的初始值为 0

只有主线程写完以后,信号量 r 的值会加一,子线程才能去读;子线程读完以后,信号量 w 的值会加一,主线程才能去写;会一直重复这个流程

程序运行结果如下图所示:

思考

进程之间是否需要进行同步与互斥?

多进程场景

多个进程共享一段内存,即:读写共享内存

此时共享内存的访问就是临界区访问

因此,需要对临界区进行保护 (防止多个进程同时写操作)

问题:是否存在跨进程使用的互斥量?

多进程内存共享

Linux 中的跨进程信号量

多进程内存共享

多进程与信号量

test5.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define SEM_NAME  "delphi_tang"
#define PATH_NAME "/home/book/Documents"
#define PROJ_ID   199

int get_shared_memory(key_t k)
{
    int ret = shmget(k, 0, 0);
    
    if( ret == -1 )
    {
        ret = shmget(k, 
                     128,  
                     IPC_CREAT | IPC_EXCL | S_IRWXU);
    } 
    
    return ret;
}

sem_t* get_sem(int v)
{
    sem_t* ret = sem_open(SEM_NAME, 0);
    
    if( ret == SEM_FAILED )
    {
        ret = sem_open(SEM_NAME, 
                       O_CREAT | O_EXCL, 
                       S_IRWXU, 
                       v);
    }
    
    return ret;
}

int main(int argc, char* argv[])
{
    key_t k = ftok(PATH_NAME, PROJ_ID);  
    char* shmaddr = NULL;
    sem_t* sem = NULL;
    int shmid = get_shared_memory(k);
    
    printf("shmid = %d\n", shmid);
    
    if( shmid == -1 )
    {
        printf("shmget error\n");
        exit(1);
    } 
    
    sem = get_sem(1);
    
    if( sem )
    {
        printf("sem is %p\n", sem);
    }
    
    shmaddr = shmat(shmid, NULL, 0);
    
    while( (argc > 1) && shmaddr )
    {
        static int i = 0;
        
        if( strcmp(argv[1], "write") == 0 )
        {
            sem_wait(sem);
            sprintf(shmaddr, "shared string %d", i++);
            printf("write: %s\n", shmaddr);
            sem_post(sem);
            usleep(1000 * 1000);
        }
        else if( strcmp(argv[1], "read") == 0 )
        {
            sem_wait(sem);
            printf("read: %s\n", shmaddr);
            sem_post(sem);
            usleep(250 * 1000);
        }
        else
        {
            break;
        }
    }

    printf("Press any key to finish process...\n");
    
    system("read -s -n 1");
    
    shmctl(shmid, IPC_RMID, NULL); 
    
    sem_close(sem);
    
    return 0;
}

第 50 行,使用 ftok() 函数用于生成System V IPC(Inter-Process Communication,进程间通信)对象(如信号量、消息队列和共享内存)的键(key)

第 53 行,get_shared_memory() 函数调用了 shmget() 函数,shmget() 是一个Linux系统调用函数,用于创建一个新的共享内存段(segment)或获取一个已存在的共享内存段。这个函数会返回一个整数类型的共享内存标识符(ID),用于在后续的系统调用中引用共享内存段

第 63 行,get_sem() 函数调用了 sem_open() 函数,sem_open() 用于打开或创建一个命名信号量

第 70 行,shmat() 是一个Linux系统调用函数,用于将一个共享内存段附加到当前进程的地址空间。这使得进程可以通过指针访问共享内存段中的数据。shmat() 函数通常在调用 shmget() 函数后使用,以便将获得的共享内存段附加到进程的地址空间

第 72 行 - 95 行,通过信号量来访问多进程中的共享内存

程序运行结果如下图所示:

运行了 2 个进程,一个进程写共享内存,一个进程读取共享内存,信号量用于同步多进程间共享内存的读写

跨进程的信号量是通过文件的方式实现的,创建出的跨进程信号量的文件位置存放在 /dev/shm/目录下面

Linux 信号量的注意事项

信号量之间不能相互初始化,也不能相互赋值 (行为未定义)

跨进程信号量通过文件的方式实现,因此涉及读写权限

sem_close() 仅仅关闭信号量,信号量未删除

sem_unlink() 延迟删除信号量 (/dev/shm/)

  • 即:所有访问信号量的信号量结束后,信号量才被删除

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

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

相关文章

USP技术提升大语言模型的零样本学习能力

大语言模型&#xff08;LLMs&#xff09;在零样本和少样本学习能力上取得了显著进展&#xff0c;这通常通过上下文学习&#xff08;in-context learning, ICL&#xff09;和提示&#xff08;prompting&#xff09;来实现。然而&#xff0c;零样本性能通常较弱&#xff0c;因为缺…

KMP算法--C语言实现

#include <stdio.h> #include <assert.h> #include <string.h> #include <stdlib.h>void GetNext(char* sub, int next[]) {int lenSub strlen(sub);next[0] -1; // 初始第一个为 -1 第二个为 0next[1] 0;int i 2;int k 0;while (i < lenSub){…

探究Android的多分辨率支持以及各种类型图标尺寸大小

术语和概念 屏幕尺寸 屏幕的物理尺寸&#xff0c;以屏幕的对角线长度作为依据&#xff08;比如 2.8寸&#xff0c; 3.5寸&#xff09;。 简而言之&#xff0c; Android把所有的屏幕尺寸简化为三大类&#xff1a;大&#xff0c;正常&#xff0c;和小。 程序可以针对这三种尺寸…

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例 引言1. UniMRCP和UmcFramework简介2. 准备工作3. unimrcpclient.xml配置文件3.1 定义SIP设置3.2 定义MRCP会话配置文件 4. C代码示例5. 测试和验证6. 故障排查7. 结论8. 参考文献 引言 在多媒体通信…

Vue单页面应用和多页面应用的区别

概念&#xff1a; SPA单页面应用&#xff08;SinglePage Web Application&#xff09;&#xff0c;指只有一个主页面的应用&#xff0c;一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面&#xff0c;对每一个功能模块组件化。单页应用跳转&#xff0c;就是切换…

STM32标准库编译流程

导入库函数 在ST官方固件库中找到STM32F10x_StdPeriph_Lib_V3.5.0.zip文件&#xff0c;解压&#xff0c;打开Libraries,接着打开STM32F10x_StdPeriph_Driver文件夹&#xff0c;继续点击src&#xff0c;看到库函数源文件&#xff1a; 将其复制到keil建立的工程的文件中&#xf…

JAVA系列 小白入门参考资料 接口

目录 接口 接口的概念 语法 接口使用 接口实现用例 接口特性 实现多个接口和实现用例 接口间的继承 接口 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本上的 USB 口&#xff0c;电源插座等。 电脑的 USB 口上&am…

在视频中使用时间卷积和半监督训练进行三维人体姿态估计

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;在视频中使用时间卷积和半监督训练进行三维人体姿态估计1、文献摘要2、提出方法2.1、时间扩张卷积模型2.2、半监督方法2.3、与传统…

【错题集-编程题】十字爆破(预处理 + 模拟)

牛客对于题目链接&#xff1a;十字爆破 (nowcoder.com) 一、分析题目 暴力模拟会超时。 预处理&#xff0c;先把每一行以及每一列的和存起来。 模拟即可&#xff0c;但是由于数据量过⼤&#xff0c;我们可以提前把每⼀⾏以及每⼀列的和存起来&#xff0c;⽅便统计总和。 二、代…

应用分层和企业规范

目录 一、应用分层 1、介绍 &#xff08;1&#xff09;为什么需要应用分层&#xff1f; &#xff08;2&#xff09;如何分层&#xff1f;&#xff08;三层架构&#xff09; MVC 和 三层架构的区别和联系 高内聚&#xff1a; 低耦合&#xff1a; 2、代码重构 controlle…

Sqlserver批量迁移Job

因为切换物理机&#xff0c;需要把数据库的作业从A机器迁移到B机器&#xff0c;数据库整体备份还原就可以了&#xff0c;数据库上的作业不会跟着带过去&#xff0c;需要手动创建&#xff0c;作业数量太多&#xff0c;逐一创建太浪费时间&#xff0c;Microsoft SQL Server Manag…

SpringBoot+Vue项目企业客户管理系统

一、前言介绍 本文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述企业客户管理系统的当前背景以及系统开…

扩展学习|国内外用户画像相关进展一览

文献来源&#xff1a;徐芳,应洁茹.国内外用户画像研究综述[J].图书馆学研究,2020(12):7-16.DOI:10.15941/j.cnki.issn1001-0424.2020.12.002. 一、用户画像的概念 用户画像概念一经提出,便被广泛应用到精准营销等领域。后来,作为一种描绘用户特征、表达用户诉求的有效工具,用户…

karpathy Let‘s build GPT

1 introduction 按照karpathy的教程&#xff0c;一步步的完成transformer的构建&#xff0c;并在这个过程中&#xff0c;加深对transformer设计的理解。 karpathy推荐在进行网络设计的过程中&#xff0c;同时利用jupyter notebook进行快速测试和python进行主要的网络的构建。 …

前端页面平滑过渡解决方案

一、问题产生 在使用图片作为页面背景时&#xff0c;无法使用transtion进行平滑过渡&#xff0c;直接切换背景又会降低使用体验。 二、解决方式 使用clip-path对背景图片裁剪配合transtion实现平滑过渡的效果 三、效果展示 网址&#xff1a;ljynet.com 四、实现方式 tem…

图像特征点检测

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

练习题(2024/5/3)

1对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false提示&#xff1a; 树中…

前端工程化04-VsCode插件设置总结(持续更)

1、输出语句log设置 log输出、平常你输出log,还必须得打一个console然后再.log()非常不方便&#xff0c;当然我们可以直接输入一个log,但是提示有两个&#xff0c;我们还得上下选择 所以我们直接采用插件的提示 一个clg就可以了 2、括号包裹提示 找到VsCode的settings.js文…

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 &#xff1e;什么是研究生入学考试&#xff1f; 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后&#xff0c;也可称研究生&#xff0c;含义为研究生学历的人。在中国大陆地区&#xff0c;普通民众一般也将硕士毕业生称…

微图乐 多种装B截图一键制作工具(仅供娱乐交流)

软件介绍 采用exe进程交互通信。全新UI界面&#xff0c;让界面更加清爽简约。支持zfb、VX、TX、Yin行、Dai款、游戏等图片生成&#xff0c;一键超清原图复制到剪辑板&#xff0c;分享给好友。适用于提高商家信誉度&#xff0c;产品销售额度。装逼娱乐&#xff0c;用微图乐。图…