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

Linux——进程间、线程间的通信

一、线程间的同步机制

线程间同步机制:让多个线程在执行某个任务时,具有先后顺序的执行。

信号量:实现线程间同步。

操作步骤
1. 定义信号量对象 :sem_t 
2. 初始化信号量 : sem_init();

 int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem:要初始化的信号量对象地址
pshared:
0 : 线程间共享
非0 : 进程间共享
value:信号量的初始值(初始资源数)
返回值:
成功:0
失败:-1
3.申请信号量: P操作 :int sem_wait(sem_t *sem);
   释放信号量:V操作:int sem_post(sem_t *sem);
也称为PV操作
4. 销毁信号量:int sem_destroy(sem_t *sem);

例:创建三个线程,分别让这三个线程按照顺序打印:A-->B-->C-->A-->B-->C

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>sem_t sem_a;
sem_t sem_b;
sem_t sem_c;void *task1(void *arg)
{   while(1){sem_wait(&sem_a);printf("A\n");sem_post(&sem_b);sleep(1);}
}void *task2(void *arg)
{   while(1){sem_wait(&sem_b);printf("B\n");sem_post(&sem_c);sleep(1);}}void *task3(void *arg)
{while(1){sem_wait(&sem_c);printf("C\n");sem_post(&sem_a);sleep(1);}}int main(int argc, char const *argv[])
{pthread_t tid[3];sem_init(&sem_a, 0, 1);sem_init(&sem_b, 0, 0);sem_init(&sem_c, 0, 0);pthread_create(&tid[0], NULL, task1, NULL);pthread_create(&tid[1], NULL, task2, NULL);pthread_create(&tid[2], NULL, task3, NULL);for(int i = 0; i < 3; ++i){pthread_join(tid[i], NULL);}sem_destroy(&sem_a);sem_destroy(&sem_b);sem_destroy(&sem_c);return 0;
}

二、死锁

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

死锁现象

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;void *threadA(void *arg) {pthread_mutex_lock(&lock1);  // 线程A获取lock1printf("Thread A: Got lock1, waiting for lock2...\n");sleep(1);  // 模拟耗时操作,确保线程B能拿到lock2pthread_mutex_lock(&lock2);  // 线程A尝试获取lock2(但lock2被线程B持有)printf("Thread A: Got both locks!\n");  // 永远无法执行到这里pthread_mutex_unlock(&lock2);pthread_mutex_unlock(&lock1);return NULL;
}void *threadB(void *arg) {pthread_mutex_lock(&lock2);  // 线程B获取lock2printf("Thread B: Got lock2, waiting for lock1...\n");sleep(1);  // 模拟耗时操作,确保线程A能拿到lock1pthread_mutex_lock(&lock1);  // 线程B尝试获取lock1(但lock1被线程A持有)printf("Thread B: Got both locks!\n");  // 永远无法执行到这里pthread_mutex_unlock(&lock1);pthread_mutex_unlock(&lock2);return NULL;
}int main() {pthread_t t1, t2;pthread_create(&t1, NULL, threadA, NULL);pthread_create(&t2, NULL, threadB, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("Program finished.\n");  // 永远不会执行到这里return 0;
}

解决方法1:按固定顺序加锁

修改 threadB,使其加锁顺序与 threadA 一致(先 lock1 后 lock2):

void *threadB(void *arg) {pthread_mutex_lock(&lock1);  // 改为先获取lock1printf("Thread B: Got lock1, waiting for lock2...\n");sleep(1);pthread_mutex_lock(&lock2);  // 再获取lock2printf("Thread B: Got both locks!\n");pthread_mutex_unlock(&lock2);pthread_mutex_unlock(&lock1);return NULL;
}

注:此时程序能正常结束,因为两个线程的加锁顺序一致,不会形成循环等待。

解决方法2:使用 pthread_mutex_trylock 避免死锁

如果某个线程发现锁被占用,就主动释放自己的锁,避免死锁:

void *threadA(void *arg) {while (1) {pthread_mutex_lock(&lock1);printf("Thread A: Got lock1, trying lock2...\n");if (pthread_mutex_trylock(&lock2) {  // 尝试获取lock2printf("Thread A: Failed to get lock2, releasing lock1...\n");pthread_mutex_unlock(&lock1);  // 释放lock1,避免死锁sleep(1);  // 稍后重试} else {printf("Thread A: Got both locks!\n");pthread_mutex_unlock(&lock2);pthread_mutex_unlock(&lock1);break;}}return NULL;
}

三、进程间的通信:IPC机制

(1)使用IPC机制的原因

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

(2)通信方法

同一主机进程间通信

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

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

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

3、socket通信
网络通信

(3)管道通信

1. 管道

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

2. 无名管道


1)无名管道操作流程:
1. 创建无名管道:pipe();                      
2. 写管道:write();                             pipefd[1] ------>写端
3. 读管道:read();                             pipefd[0] ----->读端
4. 关闭管道:close();

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

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

3)管道的特性:

      1. 写阻塞:读端和写端都存在,向管道中写数据,当管道满时,发生写阻塞。
2. 读阻塞:读端和写端都存在,从管道中读数据,若管道为空,则发生读阻塞。
3. 读返回0:当写端关闭,从管道中读数据,若管道中有数据,则读到数据;
若管道中没有数据,read则返回0,不再阻塞。
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

例:无名管道

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>int main(int argc, char const *argv[])
{int pipefd[2];int ret = pipe(pipefd);char buff[1024] = {0};if(ret < 0){perror("pipe create error");}pid_t pid = fork();if(pid > 0){close(pipefd[0]);while(1){fgets(buff, sizeof(buff), stdin);buff[strlen(buff) - 1] = '\0';write(pipefd[1], buff, strlen(buff));}close(pipefd[1]);wait(NULL);}else if(0 == pid){close(pipefd[1]);while(1){memset(buff, 0, sizeof(buff));read(pipefd[0], buff, sizeof(buff));printf("buff = %s\n", buff);}close(pipefd[0]);}else{perror("fork process error");}return 0;
}

例:有名管道

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>int main(int argc, char const *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;}while(1){char buff[1024] = {0};fgets(buff, sizeof(buff), stdin);buff[strlen(buff) - 1] = '\0';write(fd, buff, strlen(buff)); if(0 == strcmp(buff, "exit")){break;}     }close(fd);return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>int main(int argc, char const *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;}while(1){char buff[1024] = {0};memset(buff, 0, sizeof(buff));ssize_t cnt = read(fd, buff, sizeof(buff));printf("cnt = %ld, buff = %s\n", cnt, buff); if(0 == strcmp(buff, "exit")){break;}       }close(fd);remove("./myfifo");return 0;
}

四、将内存区域填充成指定的数据函数

void *memset(void *s, int c, size_t n);
功能:将内存区域填充成指定的数据
参数:
s:要填充的空间首地址
c:要填充的字符
n:要填充的字节数
返回值:
成功:返回s的首地址
失败:NULL

常见用途:

清零内存:memset(ptr, 0, size)(相当于 bzero)。

初始化数组:如 memset(arr, -1, sizeof(arr))(将 arr 所有字节设为 0xFF)。

填充特定模式:如 memset(buffer, 'A', 100)(填充 100 个 'A')。

#include <stdio.h>
#include <string.h>int main() {char str[50];memset(str, 'X', sizeof(str) - 1); // 填充 'X'str[sizeof(str) - 1] = '\0';       // 确保字符串终止printf("%s\n", str);               // 输出 49 个 'X'int arr[10];memset(arr, 0, sizeof(arr));       // 清零数组return 0;
}

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

相关文章:

  • 【C++】 using声明 与 using指示
  • 《彩色终端》诗解——ANSI 艺术解码(DeepSeek)
  • C++设计模式:建造者模式
  • 《若依》权限控制
  • ESP32小智-语音活动(VAD)检测流程
  • Pytorch GPU版本安装保姆级教程
  • 【Python面试题】描述一次解决内存泄漏的过程。如何用tracemalloc或者objgraph定位问题?什么情况下会用__slots__?
  • 【领码课堂】AI写码不再“盲跑”,方案先行,自动化高效落地
  • BOSS直聘招聘端AI辅助自动化技术研究
  • 某储备土地前期开发项目控制保护区桥梁自动化监测
  • 8.19 note
  • HashMap:源码
  • OpenLayers 入门指南【七】:加载自定义控件
  • 部署耐达讯自动化Profibus转光纤方案,变频器通信从此告别‘掉线焦虑’!“
  • Next.js数据获取
  • 飞算JavaAI智慧文旅场景实践:从景区管理到游客服务的全链路系统搭建
  • 无人机激光测距技术应用与挑战
  • 【前端进阶】UI渲染优化 - 骨架屏技术详解与多框架实现方案
  • Maven(一)
  • 做一个答题pk小程序多少钱?
  • 《红色脉-络:一部PLMN在中国的演进史诗 (1G-6G)》 第6篇 | 专题:核心网的第一次革命——从电路交换到“用户/控制面分离”
  • java17学习笔记-增强型伪随机数生成器
  • LeetCode100-438找到字符串中所有的字母异位词
  • 上网行为管理之用户认证技术和应用控制技术
  • 开源im即时通讯软件开发社交系统全解析:安全可控、功能全面的社交解决方案
  • 具身智能3全身动力学控制软件包(人形机器人)摘自Openloong社区
  • N32G430C8-串口驱动问题
  • MATLAB的实用字母识别系统实现含GUI界面
  • 软件在线安装和离线安装
  • c/c++标准库