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

news2024/11/15 15:54:17

目录

【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)//判断输入的数据是否为 quit
            break;//若是 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;
}

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

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

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

相关文章

sqlmap注入语句学习,帮助你排查SQL注入漏洞

摘要 sqlmap是一个开源的渗透测试工具&#xff0c;可以用来进行自动化检测&#xff0c;利用SQL注入漏洞&#xff0c;获取数据库服务器的权限。它具有功能强大的检测引擎&#xff0c;针对各种不同类型数据库的渗透测试的功能选项&#xff0c;包括获取数据库中存储的数据&#x…

第3章-03-Python库Requests安装与讲解

Python库Requests的安装与讲解 &#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年CSDN全站百大博主。 &#x1f3c6;数年电商行业从业经验&…

GPLGIAGQ;MMP2靶向光敏剂多肽;MMP2可降解 (cleavable) 的多肽;CAS号:109053-09-0

【GPLGIAGQ 简介】 GPLGIAGQ 是一种 MMP2 可切割的多肽,在脂质体和胶束纳米载体中都被用作刺激敏感的连接物,用于 MMP2 触发的肿瘤靶向治疗。GPLGIAGQ可用于合成光动力治疗 (PDT) 中独特的MMP2靶向光敏剂。 【中文名称】MMP2靶向光敏剂多肽&#xff1b;GPLGIAGQ 【英文名称】G…

【知识】缓存类型和策略

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 缓存读取策略 缓存写入策略 直写缓存写入策略 回写高速缓存写入策略 回写缓存写入策略 ​​​​​​缓存替换策略 先进先出 &#xff08;F…

C与C++的三种区分方式

1、单个字符的sizeof大小 在C和C中&#xff0c;单个字符&#xff08;char类型&#xff09;的大小通常是1字节&#xff08;8位&#xff09;&#xff0c;但这取决于编译器和目标平台。这是一种特别的区分方式&#xff0c;特别定义的。 2、是否有__cplusplus __cplusplus是一个预…

【html+css 绚丽Loading】000025 玄机翻转棱

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

等保2.0 | Apache Tomcat中间件测评

这里就谈谈等保2.0要求&#xff0c;对应到Apache Tomcat中间件的一些条款要求。 安装步骤略过&#xff0c;我们直接看等保中涉及的一些参数。 首先&#xff0c;做测评的时候我们先要记录相应的软件版本&#xff1a; 查看版本&#xff0c;在tomcat目录下执行/bin/catalina.sh…

是否应该使用WordPress自动更新的功能

开源程序例如WordPress&#xff0c;使许多人能够轻松创建自己的网站。然而&#xff0c;却存在一个棘手的问题是黑客攻击。开源的性质及其安全透明性让黑客、机器人和脚本小子提供了不断攻击的机会。防止WordPress网站被黑的首要方法是保持WordPress版本、主题和插件的更新。对于…

突发性网络攻击的应对策略

应对突发性网络攻击&#xff0c;推动情报工作转型是重中之重&#xff0c;核心工作在于侦测 (detecting) 与回应 (respond)。负责任和安全的情报政策、程序与工作机制是满足网络安全治理任务的信息基础。正 如美国中情局原局长约翰杜奇提到&#xff0c;“( 网络突然袭击 ) 这种…

【Linux】倒计时|进度条|git|gdb的实现

目录 一、缓冲区 1.缓冲区概念&#xff1a; 2.缓冲区作用&#xff1a; 3.缓冲区刷新策略 4.缓冲区位置 5.总结 二、实现倒计时 三、进度条版本1️⃣ 四、进度条版本2️⃣ 五、使用git命令行 六、Linux调试器-gdb使用 背景 开始使用 一、缓冲区 1.缓冲区概念&…

大模型的latency(延迟)和throughput(吞吐量)有什么区别?

1. 导入 为了快速应用大模型&#xff0c;我们需要采购商业大模型。采购前&#xff0c;对接销售时&#xff0c;我们描述了我们的场景和需求&#xff1a; Q&#xff1a;我们的prompts token数量在1500-2000之间&#xff0c;completion token数量在500左右。这种情况下&#xff…

嵌入式学习——ARM学习(2)——汇编学习

工具&#xff1a;Keil-uVision5 1、汇编 1.1 汇编的组成 指令&#xff1a;汇编语言的核心部分&#xff0c;表示 CPU 可以执行的操作&#xff0c;如数据传输、算术运算、逻辑运算等。 操作数&#xff1a;指令中用于指定操作对象的数据&#xff0c;可以是寄存器、内存地址或立即…

Qt第二课----信号和槽

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

带你速通C语言——静态变量(14)

使用静态变量 (static) 在 C 语言中是管理函数内部状态或跨函数调用保持数据的一种有效方式。理解静态变量如何工作可以帮助你编写更加复杂和可控的程序。 1.静态变量的特性 静态变量有几个关键特性&#xff1a; 持久性&#xff1a;静态变量在函数多次调用之间保持其值。它们…

霍尼韦尔护眼大路灯怎么样?书客、月影、霍尼韦尔实测数据大揭秘

霍尼韦尔大路灯怎么样&#xff1f;目前市面上的大路灯外观几乎都大同小异&#xff0c;但很多品牌在宣传方面做的很牛&#xff0c;消费者在拿到手后的体验却是大打折扣&#xff0c;跟官方宣传的真的是一个天一个地&#xff0c;相差太大。作为一个测评博主&#xff0c;很多人问我…

elasticsearch整合java使用创建索引、指定索引映射、操作添加文档、删除文档、更新文档、批量操作

前言&#xff1a; elasticsearch的整合流程可以参考&#xff1a;Elasticsearch7.15版本后新版本的接入-CSDN博客 索引 1.创建索引 Testpublic void contextLoads() throws IOException {ElasticsearchClient elasticsearchClient elasticSearchConfig.esRestClient();bool…

【Qt】表单布局QFormLayout

表单布局QFormLayout Qt 还提供了 QFormLayout , 属于是 QGridLayout 的特殊情况, 专⻔⽤于实现两列表单的布局. 这种表单布局多⽤于让⽤⼾填写信息的场景. 左侧列为提⽰, 右侧列为输⼊框 例子&#xff1a;使用QFormLayout创建表单 &#xff08;1&#xff09;设置三个label、…

数据分析及应用:如何对试卷得分做min-max归一化处理?

目录 0 问题描述 1 数据准备 2 问题分析 3 小结 0 问题描述 现有试卷信息表examination_info(exam_id试卷ID, tag试卷类别, difficulty试卷难度, duration考试时长, release_time发布时间): 试卷作答记录表exam_record(uid用户ID, exam_id试卷ID, start_time开始作答时…

强者和弱者的区别体现在面对失败上

面对成功&#xff0c;面对日常&#xff0c;每一个人都是谦谦君子温文尔雅&#xff0c;谈论困难挫折&#xff0c;不屑一顾。 这是他们的真实面目吗&#xff1f; 有的是真的&#xff0c;有的是假的。 强者视失败为磨砺意志的砥石&#xff0c;他们勇于承认不足&#xff0c;积极寻…

物联网架构之CDH集群部署

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…