当前位置: 首页 > news >正文

嵌入式第三十二课!!线程间的同步机制与进程间的通信(IPC机制)

线程间的同步机制

首先我们要明确一个概念:

多线程、多进程本质是异步执行

        为了让多个线程在执行某个任务时,具有先后顺序的执行,我们引入信息量这个概念,实现线程间的同步机制;

信号量

它的原理是这样的:

先在初始化阶段确认每个线程的信息量;

先执行的线程信息量是1,在申请后(执行线程1)信息量-1(线程1信息量为0);执行结束后释放信息量+1,使线程2的信息量为1;在申请后(执行线程2)信息量-1(线程2信息量为0);执行结束后释放信息量+1,使线程1的信息量为1……

信息量的操作步骤

1. 定义信号量对象 :sem_t (有几个线程就申请几个)
2. 初始化信号量 : sem_init();
3.申请信号量: P操作 :int sem_wait(sem_t *sem);
释放信号量:V操作:int sem_post(sem_t *sem);(释放对应的信息量)
4. 销毁信号量:int sem_destroy(sem_t *sem);

初始化信息量的函数是这样用的:

 int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem:要初始化的信号量对象地址
pshared:
0 : 线程间共享
非0 : 进程间共享
value:信号量的初始值(初始资源数)
返回值:
成功:0
失败:-1

信息量的应用练习

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>char buff[1024] = {0};sem_t sem_w;
sem_t sem_r;void *task1(void *arg)
{while (1){sem_wait(&sem_w);fgets(buff, sizeof(buff), stdin);sem_post(&sem_r);}
}void *task2(void *arg)
{while (1){sem_wait(&sem_r);printf("buff = %s\n", buff);sem_post(&sem_w);}
}int main(int argc, const char *argv[])
{pthread_t tid[2];sem_init(&sem_w, 0, 1);sem_init(&sem_r, 0, 0);pthread_create(&tid[0], NULL, task1, NULL);pthread_create(&tid[1], NULL, task2, NULL);pthread_join(tid[0], NULL);pthread_join(tid[1], NULL);sem_destroy(&sem_w);sem_destroy(&sem_r);return 0;
}

这个方法可以确保线程1的运行是早于线程2的运行;

关于锁

        我们把互斥锁、信号量、读写锁、自旋锁等统称为锁;

死锁

        死锁指的是在多线程环境中,每个执行流(线程)都有未释放的资源,且互相请求对方未释放资源,从而导致陷入永久等待状态的情况。

现象:

        现象1:忘记释放锁
现象2:重复加锁
现象3:多线程多锁,抢占锁资源不当
如:线程A获取了1锁,线程B获取了2锁,同时线程A还想获取2锁,线程B还想获取1锁

产生死锁的四个必要条件:

        (1) 互斥条件:一个资源每次只能被一个进程使用(一个执行流获取锁后,其它执行流不能再获取该锁)。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放(执行流本身使用着一把锁并不释放,还在请求别的锁)。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺(A执行流拿着锁,其它执行流不能释放)。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(多个执行流拿着对方想要的锁,并且各执行流还去请求对方的锁)。

解决方法

        1.锁一定要成对出现
2.使线程的加锁顺序一致
3.破坏环路等待条件
使用非阻塞锁,一旦线程发现请求的锁被使用,就去释放自己拥有的锁
pthread_mutex_trylock();
int sem_trywait(sem_t *sem);

进程间通信(IPC机制)

        IPC机制指的就是:interprocess communicate

进程间通信

        进程间空间独立,无法直接通信,需要IPC机制实现通信。

同一主机进程间通信        

        1、古老的通信方式
无名管道  
有名管道  
信号:进程间通知机制

        2、IPC对象通信 system v    
共享内存*:效率最高
消息队列        
信号量集 (信号灯)

主要用于不同主机进程间通信

        3、socket通信(在同一主机也可以用)
网络通信

IPC机制

1.管道

        有名管道:可以用于同一主机,任意进程间通信
无名管道:只能用于同一主机,具有亲缘关系的进程间通信(父子进程间)

2.无名管道

无名管道使用原理是这样的:

这个管道不需要定义名字,它的原理是在内核空间开辟出一段空间作为管道,创造两个文件标识符,一端专门用来读取数据,一端专门用来写入数据;

(1)无名管道操作流程

        1. 创建无名管道:pipe();
2. 写管道--》write();(文件标识符断开用文件IO就可以了)
3. 读管道--》read();
4. 关闭管道--》close();

具体的应用实例如下:

#include <stdio.h>
#include <unistd.h>int main(int argc, const char *argv[])
{int pipefd[2];int ret = pipe(pipefd);if (ret < 0){perror("pipe error");return -1;}pid_t pid = fork();if (pid > 0){//pipefd[0]  --->read	//pipefd[1]  --->writeclose(pipefd[0]);write(pipefd[1], "hello world", 11);close(pipefd[1]);wait(NULL);}else if (0 == pid){//pipefd[0] --->read//pipefd[1] --->writeclose(pipefd[1]);char buff[1024] = {0};read(pipefd[0], buff, sizeof(buff));printf("buff = %s\n", buff);close(pipefd[0]);}else{perror("fork error");}return 0;
}
(2)管道本质

       管道本质上就是内核空间中的一段缓冲区,遵循先进先出特点。
无名管道的:读端:pipefd[0]
写端:pipefd[1]
且其读写端不能交换。

       无名管道默认大小:65536bytes = 64K;

(3)管道的特性

        1. 写阻塞:读端和写端都存在,向管道中写数据,当管道满时,发生写阻塞。
2. 读阻塞:读端和写端都存在,从管道中读数据,若管道为空,则发生读阻塞。
3. 读返回0:当写端关闭,从管道中读数据,若管道中有数据,则读到数据; 若管道中没有数据,read则返回0,不再阻塞。
4. 管道破裂:读端关闭,向管道中写入数据,发生管道破裂(异常)

(4)单工、半双工与全双工

        单工:具有单向性,一个只能发,一个只能接收(广播就具有这种性质)

        半双工:一个发,一个收,当一个发完,另一个可以再发(对讲机就具有这种性质)

        全双工:两者可以同时发送,同时接收(电话)

3.有名管道

有名管道的原理如下:

这两个进程可以毫无关系,它是通过创建一个管道,来通过这个管道来关联一个文件来进行读写通信;

1)有名管道本质:

        内核空间的一段缓冲区,但这块缓冲区和一个管道文件相关联。

2)有名管道的操作流程:

       1. 创建管道文件 mkfifo、mkfifo();
2. 打开管道文件 open();
3. 写管道文件 write();
4. 读管道文件 read();
5. 关闭管道文件 close();
6. 删除管道文件 int remove(const char *pathname);

       创建管道文件的函数是这样使用的:

        int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
参数:
pathname:管道文件的名称
mode:管道文件的读写执行权限
返回值:
成功:0;
失败:-1

有名管道的应用实例如下:

进程1:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main(int argc, const char *argv[])
{int ret = mkfifo("./myfifo", 0664);	if (ret != 0 && errno != EEXIST){perror("mkfifo error");return -1;}int fd =  open("./myfifo", O_WRONLY);if (fd < 0){perror("open error");return -1;}write(fd, "hello world", 11);close(fd);//remove("./myfifo");return 0;
}

进程2:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main(int argc, const char *argv[])
{int ret = mkfifo("./myfifo", 0664);if (ret != 0 && errno != EEXIST){perror("mkfifo error");return -1;}int fd = open("./myfifo", O_RDONLY);if (fd < 0){perror("open error");return -1;}char buff[1024] = {0};ssize_t cnt = read(fd, buff, sizeof(buff));printf("cnt = %ld, buff = %s\n", cnt, buff);close(fd);remove("./myfifo");return 0;
}

记住要在通信末尾删除管道文件;

以上就是今天和大家分享的内容!!!感谢你的阅读!!!!!

http://www.dtcms.com/a/337979.html

相关文章:

  • PotPlayer使用AI生成字幕和API实时翻译
  • Redis中LRU与LFU的底层实现:字节级的精巧设计
  • 树莓派安装python第三方库如keras,tensorflow
  • day35-负载均衡
  • 智能化合同处理与知识应用平台:让合同从 “管得住” 到 “用得好”
  • C15T3
  • openssl加密里面的pem格式和rsa有什么区别?
  • 财务分析师如何提升自身专业能力:突破职业瓶颈的五年进阶规划
  • nestjs配置@nestjs/config 入门教程
  • 股票常见K线
  • 群晖nas中 打开PHP连接MariaDB 功能扩展
  • JavaSE——高级篇
  • 处理手表步数和分析用户步数数据
  • 【Tech Arch】Spark为何成为大数据引擎之王
  • 电子电气架构 --- 关于整车信息安全的一些思考
  • 2025年- H98-Lc206--51.N皇后(回溯)--Java版
  • CC攻击的安全防护方案
  • MySQL索引设计:高效查询与资源平衡指南
  • Deep Plug-And-Play Super-Resolution for Arbitrary Blur Kernels论文阅读
  • Spring Cloud LoadBalancer 最佳实践
  • PyQt流程
  • Prompt engineering(PE) —— prompt 优化如何进行?
  • 基于 PaddleDetection实现目标算法识别
  • 最终版,作者可能不再维护!
  • QNX 性能分析工具(hogs pidin tracelogger)
  • 44.安卓逆向2-补环境-使用unidbg(手动补环境)
  • JavaScript Array.prototype.at ():数组任意位置取值的新姿势
  • ReactNative开发实战——React Native开发环境配置指南
  • 使用 mdadm 创建 RAID 10(4块磁盘)
  • Buttercup:开源人工智能驱动系统检测并修补漏洞