IO进程day05(线程、同步、互斥、条件变量、进程间通信IPC)

2024-08-30 22:36

本文主要是介绍IO进程day05(线程、同步、互斥、条件变量、进程间通信IPC),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

【1】线程

1》什么是线程

1> 概念 

2> 进程和线程的区别

3> 线程资源

 2》 函数接口

1> 创建线程:pthread_create

2> 退出线程:pthread_exit

3> 回收线程资源

练习1:通过父子进程完成对文件的拷贝(cp)

练习2:输入输出,quit结束

 【2】同步

1》概念

 2》同步机制

3》函数接口

【3】互斥

1》概念

2》函数接口 

 练习:打印倒置数组功能

补充:死锁 

【4】条件变量

概述

练习:打印和倒置数组实现同步

 【5】进程间通信 IPC

1》进程间通信方式

2》无名管道

1> 特点

2> 函数接口

 3> 注意事项


【1】线程

1》什么是线程

1> 概念 

线程一个轻量级进程为了提高系统性能引入线程

线程进程参与统一调度

在同一个进程中可以创建的多个线程, 共享进程资源。

(Linux里同样用task_struct来描述一个线程

2> 进程和线程的区别

相同点:

系统提供并发执行能力

不同点:

调度和资源: 线程系统调度最小单位; 进程资源分配最小单位

地址空间方面: 同一个进程创建的多个线程共享该进程的资源;进程的地址空间相互独立

通信方面: 线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)

安全性方面: 线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全。

程序什么时候该使用线程?什么时候用进程?

对资源的管理和保护要求高,不限制开销和效率时,使用多进程。

要求效率高、速度快的高并发环境时,需要频繁创建、销毁或切换时,资源的保护管理要求不是很高时,使用多线程。

3> 线程资源

共享的资源:可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID

私有的资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈(局部变量, 返回地址)、错误号 (errno)、信号掩码和优先级、执行状态和属性

 2》 函数接口

1> 创建线程:pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

功能:创建线程

参数: thread:线程标识

            attr:线程属性, NULL:代表设置默认属性

            start_routine:函数名:代表线程函数(自己写的)

            arg:用来给前面函数传参

返回值:成功:0

失败:错误码

编译的时候需要加 -pthread 链接动态库

#include <stdio.h>
#include <pthread.h>//两个线程,主线程和新建的线程在宏观上是同时执行,微观上是通过时间片轮转的方式执行,所以两个线程的内容都会打印出来,但顺序不确定
void *handler_thread(void *arg)
{printf("in handler_thread\n");while (1); //不让线程退出return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0)  //创建线程{perror("phtread err");return -1;}printf("in main\n");while(1);  //让主线程不要结束return 0;
}

2> 退出线程:pthread_exit

void pthread_exit(void *value_ptr)

功能:用于退出线程的执行

参数:value_ptr:线程退出时返回的值

#include <stdio.h>
#include <pthread.h>// 两个线程,主线程和新建的线程在宏观上是同时执行,微观上是通过时间片轮转的方式执行,所以两个线程的内容都会打印出来,但顺序不确定
void *handler_thread(void *arg)
{pthread_exit(NULL);//退出线程printf("in handler_thread\n");while (1); // 不让线程退出return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0) // 创建线程{perror("phtread err");return -1;}printf("in main\n");while (1); // 让主线程不要结束return 0;
}

3> 回收线程资源

int pthread_join(pthread_t thread, void **value_ptr)

功能:用于等待一个指定的线程结束,阻塞函数

参数:thread:创建的线程对象,线程ID

           value_ptr:指针*value_ptr 用于指向线程返回的参数, 一般为NULL

返回值:成功 : 0

              失败:errno

int pthread_detach(pthread_t thread);

功能:让线程结束时自动回收线程资源,让线程和主线程分离,非阻塞函数

参数:thread:线程ID

非阻塞式的,例如主线程分离(detach)了线程T2,那么主线程不会阻塞在pthread_detach()pthread_detach()会直接返回,线程T2终止后会被操作系统自动回收资源

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *handler_thread(void *arg)
{printf("in handler_thread\n");sleep(3);//睡眠 3 秒之后再退出线程pthread_exit(NULL); //退出当前线程while (1); //不让线程退出return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0) //创建线程{perror("phtread err");return -1;}// pthread_join(tid, NULL);   //阻塞,等待指定的线程结束然后给其回收资源pthread_detach(tid);     //不阻塞,让指定线程结束时自动回收资源printf("in main\n");while (1); //让主线程不要结束return 0;
}

pthread_join 函数运行结果:

pthread_deacth 函数运行结果:

练习1:通过父子进程完成对文件的拷贝(cp)

通过父子进程完成对文件的拷贝(cp),父进程从文件开始到文件的一半开始拷贝,子进程从文件的一半到文件末尾。

要求:文件IO cp src dest

文件长度获取: lseek

子进程定位到文件一半: lseek

父进程怎么准确读到文件一半的位置?

fork之前打开文件,父子进程读写时,位置指针是同一个

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char const *argv[])
{int fd1, fd2;      // 定义两个文件描述符pid_t pid;         // 定义一个变量接收 fork()的返回值char buf[32] = ""; // 定义一个数组接受复制的数据内容,缓存区ssize_t n;         // 定义一个变量表示读取到的数据个数if (argc != 3) // 判断一下输入的格式是否正确,因为 cp 命令 是 3个参数{printf("err %s <srcfile> <destfile>\n", argv[0]); // 提示一下正确格式return -1;}// 执行的时候,需要写上要进行操作的两个文件,源文件和目的文件fd1 = open(argv[1], O_RDONLY);                           // 以可读可写权限打开源文件,argv[1],可读可写fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0777); // 打开目标文件,不存在创建,存在清空off_t len = lseek(fd1, 0, 2) / 2; // 定义一个变量,同时获取源文件长度的一半// 创建子进程pid = fork(); // 接收fork()返回值// 判断pid的值if (pid < 0) // pid 小于0,创建线程失败{perror("fork err\n");return -1;}else if (pid == 0) //  pid 等于 0,说明该线程是子进程,拷贝后半段{// 定位到一半的位置lseek(fd1, len, 0);            // 将源文件的光标移动到一半的位置lseek(fd2, len, 0);            // 将目标文件的光标移动到一半的位置while (n = read(fd1, buf, 32)) // 读取源文件的数据内容,放到缓存区{write(fd2, buf, n); // 将缓存区中读到的 n 个数据内容再写入到目的文件中}// 这样就完成了后半段文件的复制}else // 拷贝前半段{wait(NULL); // 等子进程读写完结束,回收子进程资源之后,父进程再进行拷贝// 定位到文件开头lseek(fd1, 0, 0); // 将源文件的光标移动到开头位置lseek(fd2, 0, 0); // 将目的文件的光标移动到开头位置//拷贝前半段和后半段有所区别,后半段的拷贝只需要从中间的位置读完并且写入即可,但是前半段不一样,前半段读的时候要判断前半段的数据个数够不够我们要读的个数,(我们这里读的是32个),也就是判断len 是否大于32,这里我们可以把len 理解为前半段还没读取的数据个数//1)如果len 大于 32,就读取32 个数据,然后让len 减去32 这样len 表示的就是前半段还没读取的数据个数//2)如果len 小于 32,就读取len 个数据,然后将len 置 0,表示读取完毕//我们采用的while判断条件是len > 0 就是我们每读取一部分数据,就让len 减掉读取的数据个数,这时 len 表示的就是前半段还没读取的数据个数,所以当 len 等于 0 时,就表示前半段已经读取完毕。while (len > 0)//前半段还有数据未读取,进入循环读取数据{if (len >= 32)//如果数据个数大于32个{read(fd1, buf, 32);//我们就直接读取32个write(fd2, buf, 32);//并向目的文件写入32个len -= 32;//前半段未读取数据减掉32}else//如果数据个数不够32个{read(fd1, buf, len);//我们就读取 len 个(还有多少就读取多少)write(fd2, buf, len);//向目的文件写入 len 个len = 0;//全部读取完毕,len 置 0}}}//关闭两个文件描述符close(fd1);close(fd2);return 0;
}

练习2:输入输出,quit结束

通过线程实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。

  1. 全局变量进行通信
  2. 加上标志位(flag),实现主线程输入一次,线程函数打印一次, int flag = 0;
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>/*练习:输入输出,quit结束
通过线程实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。
1)全局变量进行通信
2)加上标志位(flag),实现主线程输入一次,线程函数打印一次, int flag = 0;*/
int flag = 0;//定义一个标志位,用来决定两个线程执行
char buf[32];//定义一个数组用来暂时保存数据,缓存区void *handler(void *arg)//线程函数
{while (1)//死循环输出{if (flag == 1)//当标志位 flag 是 1 的时候,执行该线程的循环内容{printf("%s", buf);//打印缓存区中的内容// fputs(buf,stdout);//用标准IO 的方式输出到终端flag = 0;//这个线程执行完之后,也就是打印完缓存区内容后,就将标志位 flag 置为 0}}return NULL;
}
int main(int argc, char const *argv[])
{pthread_t tid;if (pthread_create(&tid, NULL, handler, NULL) != 0)//创建线程{perror("err\n");return -1;}while (1)//死循环输出{if (flag == 0)//当标志位 flag 为 0 时,执行该线程循环内容{scanf("%s", buf);//向缓冲区中输入数据// fgets(buf,32,stdin);//用标准IO 的方式从终端向缓存区输入数据if (strcmp(buf, "quit") == 0)//用 strcmp 比较函数判断输入的是否为 quit ,是就退出循环{break;}flag = 1;//这个线程执行完之后,也就是向缓存区输入数据后,就将标志位 flag 置为 1}}return 0;
}

 【2】同步

1》概念

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

定义:进程同步是指在多个进程执行过程中,为了防止数据的不一致性和资源的冲突,确保数据的完整性和一致性,系统对多个进程访问共享资源的顺序进行协调和控制的过程。简而言之,它是一种用于保证多个并发执行的进程能够在争夺共享资源时保持一致和协调状态的机制。

最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

异步:异步则反之,并非一定需要一件事做完在做另一件事,即不一定按照顺序执行。

 2》同步机制

通过信号量实现线程间的同步。

信号量:通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待。

信号量代表某一类资源,其值表示系统中该资源的数量:

信号量的值 > 0,表示有资源可用,可以申请到资源;

信号量的值 <= 0,表示没有资源可以用,无法申请到资源,阻塞。

信号量还是一个受保护的变量,只能通过三种操作来访问:初始化P操作(申请资源)V操作(释放资源)

sem_init:信号量初始化

sem_wait:申请资源,P操作,如果没有资源可以用阻塞等待,直到有资源可用结束阻塞资源 -1

sem_post:释放资源,V操作,非阻塞,资源 +1

3》函数接口

int sem_init(sem_t *sem, int pshared, unsigned int value)

功能:初始化信号量

参数:sem:初始化的信号量对象

          pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)

          value:信号量初值

返回值:成功 0

              失败 -1

int sem_wait(sem_t *sem)

功能:申请资源 P操作 (使信号量减 1)

参数:sem:信号量对象

返回值:成功 0

              失败 -1

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

int sem_post(sem_t *sem)

功能:释放资源  V操作(使信号量加 1)

参数:sem:信号量对象

返回值:成功 0

              失败 -1

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

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>
#include <unistd.h>char s[32];//定义一个数组用来存储输入的数据,缓存区
// 定义两个信号量对象控制两个线程
sem_t sem1;
sem_t sem2;void *handler_thread(void *arg)//新线程
{while (1)//新线程循环{sem_wait(&sem1);//因为 sem1 的初值为 0 ,所以申请不到资源,会阻塞,直到主线程执行完之后,释放 sem 资源之后,才能申请得到资源,开始执行,sem1--以后每一次循环都要等主线程释放完sem1 资源后才能再次得到资源printf("%s\n", s);//打印数组 s 中的数据sem_post(&sem2);//释放资源,sem2++}return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid; if (pthread_create(&tid, NULL, handler_thread, NULL) > 0)//创建新线程{perror("err");return -1;}if (sem_init(&sem1, 0, 0) != 0)//初始化信号量 sem1 值为 0{printf("error\n");return -1;}if (sem_init(&sem2, 0, 1) != 0)//初始化信号量 sem2 值为 1{printf("error\n");return -1;}while (1)//主线程循环{sem_wait(&sem2);//因为 sem2 的初值为 1 ,所以可以直接申请得到资源,开始执行, sem2--  以后每次循环都需要等待新线程释放完sem2 资源之后才能再次得到资源scanf("%s", s);//终端输入数据到 s 数组中if (strcmp(s, "quit") == 0)//判断输入的数据是否为 quitbreak;//若是 quit 则退出循环sem_post(&sem1);//释放资源,sem++}return 0;
}

【3】互斥

1》概念

互斥:多个线程在访问临界资源时,同一时间只能一个线程访问。

定义:互斥(Mutual Exclusion)指的是在任一时刻,只允许一个进程访问某个共享资源。这种机制确保当一个进程正在使用一个共享资源时,其他进程必须等待,直到该资源被释放。互斥的主要目的是防止多个进程同时对同一共享资源进行读写,从而避免数据不一致和冲突。

最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

临界资源:一次仅允许一个线程所使用的资源

临界区:指的是一个访问共享资源的程序片段

互斥锁(mutex):通过互斥锁可以实现互斥机制。主要是用来保护临界资源,每个临界资源都有一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

2》函数接口 

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)

功能:初始化互斥锁

参数:mutex:互斥锁

           attr: 互斥锁属性 // NULL表示缺省属性

返回值:成功 0

              失败 -1

int pthread_mutex_lock(pthread_mutex_t *mutex)

功能:申请互斥锁

参数:mutex:互斥锁

返回值:成功 0

              失败 -1

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

int pthread_mutex_unlock(pthread_mutex_t *mutex)

功能:释放互斥锁

参数:mutex:互斥锁

返回值:成功 0

              失败 -1

int pthread_mutex_destroy(pthread_mutex_t *mutex)

功能:销毁互斥锁

参数:mutex:互斥锁

 练习:打印倒置数组功能

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>int buf[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 定义一个数组
pthread_mutex_t lock;                         // 定义一个锁void *handler_Reserve(void *arg)//倒置线程
{while (1){pthread_mutex_lock(&lock);//上锁,上锁之后,在解锁之前,其他线程就无法执行//数组内容倒置for (int i = 0; i < 5; i++){int t = buf[i];buf[i] = buf[9 - i];buf[9 - i] = t;}pthread_mutex_unlock(&lock);//解锁}return NULL;
}void *handler_print(void *arg)//打印线程
{while (1){sleep(1);//让一个线程先睡眠 1 秒,这样另一个线程就可以先上锁执行,否则可能会导致冲突pthread_mutex_lock(&lock);//上锁//打印数组内容for (int i = 0; i < 10; i++)printf("%d ", buf[i]);printf("\n");//刷新缓存pthread_mutex_unlock(&lock);//解锁}return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid1, tid2;if (pthread_create(&tid1, NULL, handler_Reserve, NULL) != 0) // 创建倒置线程{perror("phtread err");return -1;}if (pthread_create(&tid2, NULL, handler_print, NULL) != 0) // 创建打印线程{perror("phtread err");return -1;}if (pthread_mutex_init(&lock, NULL) != 0) // 初始化锁 lock{perror("lock err");return -1;}pthread_join(tid1, NULL); // 阻塞,等待指定的线程结束然后给其回收资源pthread_join(tid2, NULL);//如果不加这两句,程序会直接执行到return 0,进程结束,线程也跟着结束return 0;
}

补充:死锁 

定义:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

(在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。)


死锁产生的四个必要条件

(1)互斥使用, 即当资源被一个线程使用(占有)时,别的线程不能使用

(2)不可抢占资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

(3)请求和保持即当资源请求者在请求其他的资源的同时保持对自己原有资源的占有。

(4)循环等待条件即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一种头尾相接的循环等待资源关系。


注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

【4】条件变量

概述

条件变量(cond)用于在线程之间传递信号,以便某些进程可以等待某些条件发生。当某些条件发生时,条件变量会发出信号,使等待该条件的线程可以恢复执行。

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用

       当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待。

pthread_cond_init(&cond,NULL); //初始化条件变量

使用需要上锁:

pthread_mutex_lock(&lock); //上锁

判断条件

pthread_cond_wait(&cond, &lock); //阻塞等待条件产生,没有条件产生时阻塞,同时解锁,当条件产生时结束阻塞,再次上锁

//执行任务

pthread_mutex_unlock(&lock); //解锁

pthread_cond_signal(&cond); //产生条件,不阻塞

pthread_cond_destroy(&cond); //销毁条件变量

注意: 必须保证让pthread_cond_wait先执行,pthread_cond_signal再产生条件

练习:打印和倒置数组实现同步

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>int buf[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 定义一个数组
pthread_mutex_t lock;                         // 定义一个锁
pthread_cond_t cond;                          // 定义一个条件变量
void *handler_Reserve(void *arg)              // 倒置线程
{while (1){pthread_mutex_lock(&lock); // 上锁,上锁之后,在解锁之前,其他线程就无法执行pthread_cond_wait(&cond, &lock);//如果没条件产生解锁并阻塞,等条件产生后结束阻塞并上锁// 数组内容倒置for (int i = 0; i < 5; i++){int t = buf[i];buf[i] = buf[9 - i];buf[9 - i] = t;}pthread_mutex_unlock(&lock); // 解锁}return NULL;
}void *handler_print(void *arg) // 打印线程
{while (1){sleep(1);                  // 让一个线程先睡眠 1 秒,这样另一个线程就可以先上锁执行,否则可能会导致冲突pthread_mutex_lock(&lock); // 上锁// 打印数组内容for (int i = 0; i < 10; i++)printf("%d ", buf[i]);printf("\n"); // 刷新缓存pthread_cond_signal(&cond);//产生条件pthread_mutex_unlock(&lock); // 解锁sleep(1);}return NULL;
}int main(int argc, char const *argv[])
{pthread_t tid1, tid2;if (pthread_create(&tid1, NULL, handler_Reserve, NULL) != 0) // 创建倒置线程{perror("phtread err");return -1;}if (pthread_create(&tid2, NULL, handler_print, NULL) != 0) // 创建打印线程{perror("phtread err");return -1;}if (pthread_mutex_init(&lock, NULL) != 0) // 初始化锁 lock{perror("lock err");return -1;}if (pthread_cond_init(&cond, NULL) != 0) // 初始化条件变量 cond{perror("cond err\n");return -1;}pthread_join(tid1, NULL); // 阻塞,等待指定的线程结束然后给其回收资源pthread_join(tid2, NULL);// 如果不加这两句,程序会直接执行到return 0,进程结束,线程也跟着结束pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);return 0;
}

 【5】进程间通信 IPC

1》进程间通信方式

(1)早期进程间通信

无名管道(pipe)有名管道(fifo)信号(signal)

(2)system V PIC

共享内存(share memory)信号灯集(semaphore)消息队列(message queue)

(3)BSD:

套接字(socket)

2》无名管道

1> 特点

(1)只能用于具有亲缘关系的进程之间的通信

(2)半双工的通信模式,具有固定的读端fd[0]和写端fd[1]。

(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。

(4)管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

2> 函数接口

int pipe(int fd[2])

功能:创建无名管道

参数:文件描述符 fd[0]:读端 fd[1]:写端

返回值:成功 0

              失败 -1

#include <stdio.h>
#include <unistd.h>int main(int argc, char const *argv[])
{char buf[65536] = "";int fd[2] = {0}; //fd[0]代表读端,fd[1]代表写端if (pipe(fd) < 0){perror("pipe err");return -1;}printf("%d %d\n", fd[0], fd[1]);//结构类似队列,先进先出//1. 当管道中无数据时,读阻塞。// read(fd[0], buf, 32);// printf("%s\n", buf);//但是关闭写端就不一样了//当管道中有数据关闭写端可以读出数据,无数据时关闭写端读操作会立即返回。// write(fd[1], "hello", 5);// close(fd[1]);// read(fd[0], buf, 32);// printf("%s\n", buf);//2. 当管道中写满数据时,写阻塞,管道空间大小为64K// write(fd[1], buf, 65536);// printf("full!\n");//write(fd[1], "a", 1);  //当管道写满时不能再继续写了会阻塞//写满一次之后,当管道中至少有4K空间时(也就是读出4K),才可以继续写,否则阻塞。// read(fd[0], buf, 4096); //换成4095后面再写就阻塞了,因为不到4K空间// write(fd[1], "a", 1);//3. 当读端关闭,往管道中写入数据无意义,会造成管道破裂,进程收到内核发送的SIGPIPE信号。close(fd[0]);write(fd[1], "a", 1);printf("read close\n");return 0;
}

gdb调试可以看见管道破裂信号:

gcc -g xx.c

gdb a.out

r

 3> 注意事项

(1)当管道中无数据时,读操作会阻塞

管道中有数据,将写端关闭,可以将数据读出

管道中无数据,将写端关闭,读操作会立即返回

(2)管道中装满(管道大小64K)数据写阻塞,一旦由 4k 空间,写继续

(3)只有在管道的读端存在时,向管道中写入数据才有意义,否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{char buf[32];//定义一个数组int fd[2];//定义两个文件描述符if (pipe(fd) < 0)//创建管道并判断{perror("pipe err\n");return -1;}printf("%d %d\n", fd[0], fd[1]);//打印一下文件描述符int pid;pid = fork();//创建父子进程if (pid < 0)//创建失败{perror("fork err\n");return -1;}else if (pid == 0)//子进程{while (1){read(fd[0], buf, 32);//从读端读取管道中的数据到buf中printf("%s\n", buf);//打印buf中的数据}}else//父进程{while (1){scanf("%s", buf);//从终端输入数据到buf中if (strcmp(buf, "quit") == 0)//判断输入的是否为 quit,若是,则退出循环{break;}write(fd[1], buf, 32);//将 buf 中的数据从写端写到管道中}}return 0;
}

今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!

这篇关于IO进程day05(线程、同步、互斥、条件变量、进程间通信IPC)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1122126

相关文章

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

java 进程 返回值

实现 Callable 接口 与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。 public class MyCallable implements Callable<Integer> {public Integer call() {return 123;}} public static void main(String[] args

springboot体会BIO(阻塞式IO)

使用springboot体会阻塞式IO 大致的思路为: 创建一个socket服务端,监听socket通道,并打印出socket通道中的内容。 创建两个socket客户端,向socket服务端写入消息。 1.创建服务端 public class RedisServer {public static void main(String[] args) throws IOException {

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(