LinuxC编程——线程间通信

news2024/10/2 8:22:31

目录

  • 一、同步的概念
  • 二、同步机制
    • 2.1 信号量
      • 2.1.1基础概念
      • 2.1.2 函数接口
      • 2.1.3 例子
    • 2.2 互斥锁
      • 2.2.1 几个概念
      • 2.2.2 函数接口
      • 2.2.3 练习
    • 2.3 条件变量
      • 2.3.1 步骤
      • 2.3.2 函数
      • 2.3.3 练习

我们知道,一个进中的所有线程共享进程的资源,所以可以通过在进程中定义全局变量来完成进程中线程间的通信,但是,当在同一内存空间运行多个线程时,要注意一个基本的问题,就是不要让线程之间互相破坏。例如,我们要实现两个线程要更新两个变量的值,一个线程要把两个变量的值都设成0,另一个线程要把两个变量的值都设成1。 如果两个线程同时要做这件事情,结果可能是,一个变量的值是0;另一个变量的值是1。这是因为正好在第1个线程把第1个变量设为0后,时间片到,CPU切换第2个线程,第2个线程将把两个变量都设成1,然后CPU再切换线程,第1个线程恢复运行,把第2个变量设成0。结果就是,一个变量的值是0,另一个变量的值是1。

因此需要同步机制来进行制约。
在System V IPC机制中提供了信号量来实现进程或线程之间的通信。此外按照POSIX标准,POSIX提供了两种类型的同步机制,它们是互斥锁(Mutex)条件变量(condition Variable)

一、同步的概念

同步是指多个 任务(线程)按照约定的顺序相互配合完成一件事。

二、同步机制

2.1 信号量

2.1.1基础概念

  • 通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待
  • 信号量代表某一类资源,其值表示系统中该资源的数量,信号量值>0,表示有资源可以用,可以申请到资源,继续执行程序;信号量值<=0,表示没有资源可以用,无法申请到资源,阻塞。
  • 信号量是一个受保护的变量,只能通过三种操作来访问:初始化sem_init、P操作(申请资源)sem_wait、V操作(释放资源)sem_post

2.1.2 函数接口

  1. 初始化信号量:sem_init
    int sem_init(sem_t *sem, int pshared, unsigned int value)

    • 功能:初始化信号量
    • 参数:
      • sem:初始化的信号量对象
      • pshared:信号量共享的范围(0:线程间使用 ,非0:进程间使用)
      • value:信号量初值
    • 返回值:成功 0;失败 -1
  2. 申请资源:sem_wait
    int sem_wait(sem_t *sem)

    • 功能:申请资源 P操作
    • 参数:sem:信号量对象
    • 返回值:成功;失败 -1

    此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞。

  3. 释放资源:sem_post
    int sem_post(sem_t *sem)

    • 功能:释放资源 V操作
    • 参数:sem:信号量对象
    • 返回值:成功 0;失败 -1

    注:释放一次信号量的值加1,函数不阻塞

2.1.3 例子

  1. 测试信号量<0时,进程(线程)阻塞
    在这里插入图片描述
  2. 通过线程实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,输入一行数据打印一行数据,当输入quit结束程序。
/*
    练习:使用信号量实现同步,即通过线程实现数据的交互,主线程循环从终端输入,
        线程函数将数据循环输出,当输入quit结束程序。
    要点:
        信号量初值的设定:初始化信号量为0,是为了让打印线程开始申请不到资源
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h> 

char buf[32] = {0};
sem_t sem;

void *handler(void *arg) //线程函数循环输出
{
    while (1)
    {
        sem_wait(&sem); //P  申请资源
        if(strcmp(buf,"quit")==0)
        {
            pthread_exit(NULL);
        }
        printf("buf:%s\n",buf);
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
    pthread_t tid; //创建线程
    if(pthread_create(&tid,NULL,handler,NULL) != 0)
    {
        perror("pthread_create err");
        return -1;
    }
    //初始化信号量为0,是为了让打印线程开始申请不到资源
    if(sem_init(&sem,0,0)<0)
    {
        perror("sem_init err\n");
        return -1;
    }
    while(1)
    {
        // scanf("%s",buf);
        fgets(buf,32,stdin);
        if(buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1] = '\0';
        sem_post(&sem); // V  释放资源
        if(strcmp(buf,"quit")==0)
        {
            break;
        }
    }
    pthread_join(tid, NULL);
    sem_destroy(&sem);
    return 0;
}

注意:
(1)sem_t sem定义的信号量在主线程和新线程都要使用,故要定义为全局变量。
(2)信号量初值的设定:初始化信号量为0,是为了让打印线程开始申请不到资源,使得主线程获取到数据后先执行。
(3)在终端获取数据可以用scanf或者fgets,但是要注意一点就是使用fgets时,若不加处理会出现输入quit不能终止程序执行,原因是fgets会将换行作为字符捕获,这时的buf内容为quit\n\0,在使用strcmp(buf,“quit”)进行比较时,quit\n\0和quit\0并不相等。处理方法可以是将buf中quit\n\0的倒数第二个字符’\n’给替换掉,使得strcmp(buf,“quit”)可以比较成功。

2.2 互斥锁

2.2.1 几个概念

  • 临界资源:一次仅允许一个进程所使用的资源
  • 临界区:指的是一个访问共享资源的程序片段
  • 互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
  • 互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

2.2.2 函数接口

  1. 初始化互斥锁:pthread_mutex_init
    int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)

    • 功能:初始化互斥锁
    • 参数
      • mutex:互斥锁
      • attr: 互斥锁属性 // NULL表示缺省属性
    • 返回值:成功 0;失败 -1
  2. 申请互斥锁:pthread_mutex_lock
    int pthread_mutex_lock(pthread_mutex_t *mutex)

    • 功能:申请互斥锁
    • 参数:mutex:互斥锁
    • 返回值:成功 0;失败 -1

    注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回

  3. 释放互斥锁:pthread_mutex_unlock
    int pthread_mutex_unlock(pthread_mutex_t *mutex)

    • 功能:释放互斥锁
    • 参数:mutex:互斥锁
    • 返回值:成功 0;失败 -1
  4. 销毁互斥锁:pthread_mutex_destroy
    int pthread_mutex_destroy(pthread_mutex_t *mutex)

    • 功能:销毁互斥锁
    • 参数:mutex:互斥锁

2.2.3 练习

通过两个线程实现数组倒置,线程1用于循环倒置,线程2用于循环打印。用互斥锁实现同步

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

int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
pthread_mutex_t lock;

void *reverse(void *arg)
{
    int temp;
    while (1)
    {
        pthread_mutex_lock(&lock);
        for (int i = 0; i < 10/2; i++)
        {
            temp = a[i];
            a[i] = a[9-i];
            a[9-i] = temp;
        }
        pthread_mutex_unlock(&lock);
    }
}
void *print(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&lock);
        for (int i = 0; i < 10; i++)
        {
            printf("%d ", a[i]);
        }
        printf("\n");
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, reverse, NULL) != 0)
    {
        perror("pthread_create tid1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, print, NULL) != 0)
    {
        perror("pthread_create tid2 error");
        return -1;
    }

    if (pthread_mutex_init(&lock, NULL))
    {
        perror("mutex err");
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&lock);

    return 0;
}

在这里插入图片描述

这个程序可以实现循环倒置与打印,但是不能实现倒置一次打印一次(下面的条件变量可以实现)。因为两个线程并不是交替执行,而是谁抢到时间片谁执行。比如有可能倒置线程先抢到时间片先执行,然后打印线程抢到时间片执行两次,等等其他无次序交替执行

2.3 条件变量

2.3.1 步骤

  1. pthread_cond_init:初始化
  2. pthread_cond_wait:阻塞等待条件产生,若没有条件产生会阻塞并且解锁,当有条件产生,再次上锁。所以在使用时先上锁再调用pthread_cond_wait
  3. pthread_cond_signal:产生条件,不阻塞
  4. pthread_cond_destory:销毁条件变量

2.3.2 函数

  1. 初始化条件变量:pthread_cond_init
    int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

    • 功能:初始化条件变量
    • 参数:
      • cond:是一个指向结构pthread_cond_t 的指针
      • restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
    • 返回值:成功:0 ;失败:非0
  2. 等待信号的产生:pthread_cond_wait
    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

    • 功能:等待信号的产生
    • 参数
      • restrict cond:要等待的条件
      • restrict mutex:对应的锁
    • 返回值:成功:0,失败:不为0

    注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁

  3. 产生条件变量:pthread_cond_signal
    int pthread_cond_signal(pthread_cond_t *cond);

    • 功能:给条件变量发送信号
    • 参数:cond:条件变量值
    • 返回值:成功:0,失败:非0

    注:必须等待pthread_cond_wait函数先执行,再产生条件

  4. 销毁条件变量:pthread_cond_destroy
    int pthread_cond_destroy(pthread_cond_t *cond);

    • 功能:将条件变量销毁
    • 参数:cond:条件变量值
    • 返回值:成功:0, 失败:非0

2.3.3 练习

通过两个线程实现数组倒置,线程1用于循环倒置,线程2用于循环打印,实现终端间隔1秒交替循环输出,先输出倒置的数组。用互斥锁+条件变量实现此同步

/*练习:.通过两个线程实现数组倒置,线程一用于循环倒置,线程二用于循环打印。
用互斥锁 + 条件变量实现同步
int a[10] = {0,1,2,3,4,5,6,7,8,9};
*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
pthread_mutex_t lock;
pthread_cond_t cond;

void *reverse(void *arg)
{
    int temp;
    while (1)
    {
        sleep(1); //保证print先抢到锁,让其阻塞等待信号
        pthread_mutex_lock(&lock);
        for (int i = 0, j = 9; i < j; i++, j--)
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        pthread_cond_signal(&cond);  //产生条件
        pthread_mutex_unlock(&lock); //解锁
    }
    pthread_exit(NULL);
    return NULL;
}
void *print(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&lock);
        // 阻塞等待条件产生,若没条件,则解锁,条件到来解除阻塞上锁
        pthread_cond_wait(&cond, &lock);
        for (int i = 0; i < 10; i++)
        {
            printf("%d ", a[i]);
        }
        printf("\n");
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(NULL);
    return NULL;
}
int main(int argc, char const *argv[]) //主进程
{
    pthread_t tid1;
    pthread_t tid2;
    if (pthread_mutex_init(&lock, NULL)) //初始化互斥锁
    {
        perror("mutex_init err");
        return -1;
    }
    if (pthread_cond_init(&cond, NULL)) //初始化条件变量
    {
        perror("cond_init err");
        return -1;
    }
    // 创建线程
    if (pthread_create(&tid1, NULL, reverse, NULL) != 0)
    {
        perror("pthread_create tid1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, print, NULL) != 0)
    {
        perror("pthread_create tid2 error");
        return -1;
    }
    // 阻塞回收线程
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
    return 0;
}

在这里插入图片描述
注:reverse线程函数里的sleep(1)放在加锁的上面,是为了保证print线程先抢到锁,让其在pthread_cond_wait处阻塞等待条件信号(pthread_cond_signal)产生,然后再reverse线程得到执行,实现数组的倒置,保证第一次的输出结果是倒置后的数据。

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

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

相关文章

Jetson nano镜像备份

首先我们要做的准备工作有&#xff1a;含有镜像的 SD 卡、读卡器、安装了 ubuntu 环境的电脑。 备份步骤&#xff1a; 1、把含有镜像的卡用读卡器插到硬盘剩余空间大于 64G&#xff08;具体根据镜像大小使用&#xff09; 的 Ubuntu 电脑上&#xff0c;注意这里不能使用虚拟机…

自建机房还是选择云服务器?以腾讯云为例

大企业是选择自购服务器自建机房还是使用腾讯云服务器&#xff1f;都说企业上云是趋势&#xff0c;自建机房是一次性支出&#xff0c;上云租赁云服务器等产品需要年年续费&#xff0c;大型企业有必要把数据中心迁移上云吗&#xff1f;腾讯云服务器网想说&#xff0c;自建机房购…

opencv基础59-霍夫变换原理讲解及示例-cv2.HoughLines()->(直线,圆形检测)

霍夫变换是一种在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换采用类似于投票的方式来获取当前图像内的形状集合&#xff0c;该变换由 Paul Hough&#xff08;霍夫&#xff09;于 1962 年首次提出。 最初的霍夫变换只能用于检测直线&#xff0c;经过发展后&#xff0…

PAT 1085 Perfect Sequence

个人学习记录&#xff0c;代码难免不尽人意 Sample Input: 10 8 2 3 20 4 5 1 6 7 8 9 Sample Output: 8 #include<cstdio> #include<iostream> #include<vector> #include<algorithm> #include<string> #include<map> #include<cmath&…

CAP理论与MongoDB一致性,可用性的一些思考

正文 大约在五六年前&#xff0c;第一次接触到了当时已经是hot topic的NoSql。不过那个时候学的用的都是mysql&#xff0c;Nosql对于我而言还是新事物&#xff0c;并没有真正使用&#xff0c;只是不明觉厉。但是印象深刻的是这么一张图片&#xff08;后来google到图片来自这里&…

打造个性化观影盛宴:对实时多兴趣召回的探索

包括 Tubi 在内的广告型点播流媒体服务&#xff0c;正在成为免费在线消费娱乐的重要组成部分。Tubi 拥有一个囊括电视剧、电影、体育和娱乐直播频道的海量视频内容库&#xff1b;同时&#xff0c;Tubi 为观众提供个性化的视频推荐&#xff0c;帮助观众快速找到想看的视频内容&a…

从零实战SLAM-第二课(SLAM中的基础数学)

空间数据的表达方式&#xff1a;点和向量两种形式。 向量的内积&#xff0c;也叫做点乘&#xff0c;是逐点相乘后累加&#xff0c;最终结果是一个标量&#xff0c;物理意义是一个向量在另一个向量上的投影。 外积&#xff0c;也叫做叉乘&#xff0c;两个向量拼起来成&#xff0…

vue二进制下载

封装axios&#xff0c;/api/request import axios from axios import store from /store import Vue from vue import { Message, MessageBox } from element-uiimport { getToken } from /utils/authaxios.defaults.headers[Content-Type] application/json;charsetutf-8 co…

同一局域网共享一个打印机方法

文章目录 需求描述设备连接情况配置网络凭证 需求描述 pc2想直接打印&#xff0c;而不是每次存到u盘&#xff0c;再拿到pc1&#xff0c;打印&#xff0c;实现本机打印 设备连接情况 配置 &#xff08;1&#xff09;pc1设置 ①共享打印机操作 控制面板——>设备和打印机—…

HC32L110B6芯片测试

到货之后&#xff0c;直观上感觉的确很小&#xff0c;小包装盒里面还装了说明书。 下载器单独在一个盒里面&#xff0c;但是这个T-U2T没用上&#xff0c;还是用的STLINK。 开发之前先去网上找了一些别人遇到的坑&#xff0c;的确不少。 涉及的方面也是挺全的&#xff0c;供电、…

1. 如何爬取自己的CSDN博客文章列表(获取列表)(博客列表)(手动+python代码方式)

文章目录 写在最前步骤打开chrome浏览器&#xff0c;登录网页按pagedown一直往下刷呀刷呀刷&#xff0c;直到把自己所有的博文刷出来然后我们按F12&#xff0c;点击选取元素按钮然后随便点一篇博文&#xff0c;产生如下所示代码然后往上翻&#xff0c;找到头&#xff0c;复制然…

DC-9靶机(端口敲门服务Knockd)

DC-9靶机地址 信息收集 主机发现 靶机MAC&#xff1a;00:0C:29:5A:C1:F4 arp-scan -l端口扫描 nmap -A -p- 192.168.80.142访问80端口 目录爆破 dirsearch -u 192.168.80.139 -i 200点击页面上的四个标签&#xff0c;发现 有个搜索 框&#xff0c;有个登录框 先用bp抓个包…

atxserver2环境搭建

1. 卸载python3.11.4版本 $sudo rm -rf /Library/Frameworks/Python.framework/Versions/3.11/ $sudo rm -rf /Applications/Python\ 3.11/ 第三步&#xff1a;删除指向python的链接 cd /usr/local/bin/ ls -l /usr/local/bin | grep /Library/Frameworks/Python.framework/…

利用logstash将graylog日志传输到kafka中

1.graylog配置输出 在System-outputs&#xff0c;选择GELF Output&#xff0c;填写如下内容&#xff0c;其它选项默认 在要输出的Stream中&#xff0c;选择Manage Outputs 选择GELF Output&#xff0c;右边选择刚才创建好的test。 2.安装logstash&#xff0c;作为中间临时…

Vue 整合 Element UI 、路由嵌套和参数传递(五)

一、整合 Element UI 1.1 工程初始化 使用管理员的模式进入 cmd 的命令行模式&#xff0c;创建一个名为 hello-vue 的工程&#xff0c;命令为&#xff1a; # 1、目录切换 cd F:\idea_home\vue# 2、项目的初始化&#xff0c;记得一路的 no vue init webpack hello-vue 1.2 安装…

记录一次使用python调用java代码

Python调用Java代码的主要原理是通过使用Java虚拟机&#xff08;JVM&#xff09;和相关的库/工具实现的。 在Python中&#xff0c;可以使用以下几种方式来调用Java代码&#xff1a; 使用subprocess模块&#xff1a;可以通过subprocess模块来启动一个子进程&#xff0c;并在子进…

OpenGL纹理

纹理采样器----纹理坐标 只有纹理坐标&#xff0c;纹理没有作用。 纹理坐标是在顶点着色器中设置&#xff0c;需要传入片段着色器&#xff0c;在片段着色器中需要定义纹理采样器。 然后调用texture函数利用采样器和纹理坐标对纹理进行采样。 我们使用GLSL内建的texture函数…

大模型落地金融业,想象力在哪?

金融大模型的难点在于&#xff0c;能否在产业中扎得更深&#xff1b;其颠覆性也更建立在&#xff0c;纵深到产业中去&#xff0c;赋能金融行业的长尾场景发展&#xff0c;以及重拾“金融信任”。 作者|思杭 编辑|皮爷 出品|产业家 “从经济角度讲&#xff0c;整个金融业…

界面设计用什么工具好?还不知道这5个吗?

无论是在APP设计&#xff0c;还是网站设计中&#xff0c;界面设计都是非常重要的&#xff0c;今天本文将为大家推荐5个优质的界面设计软件&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计是新一代界面设计软件&#xff0c;它不仅为设计师提供了精细的矢量编辑功能…

纯C#使用Visionpro工具1

各个工具的程序集名称 一般分类 一般情况是去掉Tool和Cog就是命名空间&#xff0c;如CogBlobTool对应于Cognex.Visionpro.Blob 也有特殊情况 忘了怎么办 可以借用ToolBlock引入工具后打开高级脚本查看 了解工具类和对象