Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)

条件变量

条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

主要应用函数:

  1. pthread_cond_init 函数
  2. pthread_cond_destroy 函数
  3. pthread_cond_wait 函数
  4. pthread_cond_timedwait 函数
  5. pthread_cond_signal 函数
  6. pthread_cond_broadcast 函数
  7. 以上 6 个函数的返回值都是:成功返回 0, 失败直接返回错误号。
  8. pthread_cond_t 类型 用于定义条件变量
  9. pthread_cond_tcond;

pthread_cond_init 函数

初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrictcond,const pthread_condattr_t *restrictattr);
参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可
也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t	cond=PTHREAD_COND_INITIALIZER;

pthread_cond_destroy 函数

销毁一个条件变量

int	pthread_cond_destroy(pthread_cond_t		*cond);

pthread_cond_wait 函数

阻塞等待一个条件变量

int	pthread_cond_wait(pthread_cond_t	*restrictcond,pthread_mutex_t	*restrictmutex);
函数作用:
  1. 阻塞等待条件变量 cond(参 1)满足
  2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
  3. 1.2.两步为一个原子操作。
  4. 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);

pthread_cond_timedwait 函数

限时等待一个条件变量

int pthread_cond_timedwait(pthread_cond_t	 *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrictabstime);

参 3: 参看 mansem_timedwait 函数,查看 struct timespec 结构体。

struct	timespec{time_t	tv_sec; /*seconds*/ 秒long tv_nsec; /*nanosecondes*/ 纳秒 } 

形参 abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。
而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。

struct	timespect={1,0}; 
pthread_cond_timedwait(&cond,&mutex,&t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去) 

正确用法:

time_tcur=time(NULL); 获取当前时间。structtimespect; 定义 timespec 结构体变量 tt.tv_sec=cur+1; 定时 1 秒pthread_cond_timedwait(&cond,&mutex,&t); 传参 

setitimer 函数还有另外一种时间类型:

 struct	timeval{time_t tv_sec; /*seconds*/ 秒 suseconds_ttv_usec; /*microseconds*/ 微秒};

pthread_cond_signal 函数

唤醒至少一个阻塞在条件变量上的线程

int	pthread_cond_signal(pthread_cond_t*cond);

pthread_cond_broadcast 函数

唤醒全部阻塞在条件变量上的线程

int	pthread_cond_broadcast(pthread_cond_t*cond);

生产者消费者条件变量模型

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定 有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚), 生产向其中添加产品,消费者从中消费掉产品。

在这里插入图片描述

/*借助条件变量模拟  生产者--消费者问题*/
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdio.h>
#include<string.h>
/*链表作为共享数据,需要被互斥量保护*/
struct msg{struct msg *next;int num;
};struct msg *head;
struct msg *mp;/*静态初始化  一个条件变量 和一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *consumer(void *p)
{for(;;){pthread_mutex_lock(&lock);  //头指针为空,说明没有结点 while(head == NULL){pthread_cond_wait(&has_product,&lock); //判断条件变量是否满足}mp = head;head = mp->next;        //模拟消费掉一个产品pthread_mutex_unlock(&lock);printf("---Consume ---%d\n",mp->num);free(mp);sleep(rand() %  5);}
}
void *producer()
{for(;;){mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1;    //模拟生产一个产品printf("--Produce ---%d\n",mp->num);pthread_mutex_lock(&lock);mp->next=head;              //头插法head = mp;pthread_mutex_unlock(&lock); //释放pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程>唤醒sleep(rand() % 5);}
}int main(int argc,char *argv[])
{pthread_t pid,cid;  //pid生产者ID  cid消费者IDsrand(time(NULL));pthread_create(&pid,NULL,producer,NULL); //生产者pthread_create(&cid,NULL,consumer,NULL); //消费者pthread_join(pid,NULL);pthread_join(cid,NULL);return 0;
}   

在这里插入图片描述

条件变量是搭配互斥锁一起使用的
  1. 因为条件变量实现同步只提供等待与唤醒功能,并没有提供条件判断的功能,因此条件判断需要用户实现,但是条件的操作是一个临界资源的操作,因此需要受保护,需要在条件判断之前加锁
  2. 如果加锁成功后,因为条件不满足而陷入休眠,就会导致卡死(因为另一方因为无法获取锁,而导致无法促使条件满足),因此需要在休眠之前解锁;并且解锁与休眠必须是原子操作
  3. 被唤醒之后,即将对临界资源进行操作,但是被操作前还要进行保护加锁
  4. 所以pthread_cond_wait集合了三步原子操作:解锁–>等待–>被唤醒后加锁

条件变量的优点

  1. 相较于 mutex 而言,条件变量可以减少竞争。
  2. 如直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚 (链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引 起消费者之间的竞争。提高了程序效率。

生产者与消费者模型(线程安全队列)

一个场所,两种角色,三种关系

功能:

  1. 解耦和(两个关系之间紧密)
  2. 支持忙闲不均
  3. 支持并发

三者关系

生产者–生产者:互斥
消费者–消费者:互斥
生产者–消费者:同步+互斥

在这里插入图片描述

/*生产者与消费者模型队列实现                                                                                                                                                                                     * 1.实现线程安全的队列,对外提供线程安全的数据入队和出队操作* 2.创建线程,分别作为生产者与消费者数据入队或数据出队*/#include<iostream>
#include<queue>
#include<pthread.h>#define MAX_QUEUE 10
class BlockQueue
{public:BlockQueue(int cap = MAX_QUEUE):_capacity(cap){//初始化队列pthread_mutex_init(&_mutex,NULL);pthread_cond_init(&_cond_con,NULL);pthread_cond_init(&_cond_pro,NULL);}   ~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond_con);pthread_cond_destroy(&_cond_pro);}   //入队void QueuePush(int data){QueueLock();while(QueueIsFull()){ //队列满了ProWait();    //生产者等待}   _queue.push(data);ConWakeUp();QueueUnLock();}   void QueuePop(int *data){QueueLock();while(QueueIsEmpty()){ConWait();}   *data = _queue.front();//获取队列头结点_queue.pop();//结点出队ProWakeUp();QueueUnLock();}private://队列加锁void QueueLock(){pthread_mutex_lock(&_mutex);}//队列解锁void QueueUnLock(){pthread_mutex_unlock(&_mutex);}//消费者等待void ConWait(){pthread_cond_wait(&_cond_con,&_mutex);}//消费者唤醒void ConWakeUp(){pthread_cond_signal(&_cond_con);}//生产者等待void ProWait(){pthread_cond_wait(&_cond_pro,&_mutex);}//生产者唤醒void ProWakeUp(){pthread_cond_signal(&_cond_pro);}//判断队列是否为空bool QueueIsFull(){return (_capacity == _queue.size());}//队列是否是满的bool QueueIsEmpty(){return _queue.empty();}private:std::queue<int>_queue;//创建队列int _capacity;//队列结点最大数量                          //线程安全实现成员pthread_mutex_t _mutex;pthread_cond_t _cond_pro;pthread_cond_t _cond_con;
};void *thr_consumer(void *arg){BlockQueue *q = (BlockQueue *)arg;while(1){int data;q->QueuePop(&data);std::cout<<"consumer"<<pthread_self() <<" get data:"<< data <<std::endl;}return NULL;
}int i = 0; //必须受保护
pthread_mutex_t mutex;void *thr_productor(void *arg){BlockQueue *q = (BlockQueue *)arg;while(1){pthread_mutex_lock(&mutex);q->QueuePush(i++);pthread_mutex_unlock(&mutex);std::cout<<"productor:" <<pthread_self() <<"put data:"<< i <<std::endl;}return NULL;
}int main(int argc,char *argv[])
{BlockQueue q;pthread_t ctid[4],ptid[4];int i,ret;pthread_mutex_init(&mutex,NULL);for(i = 0;i < 4; i++){        ret = pthread_create(&ctid[i],NULL,thr_consumer,(void *)&q);if(ret != 0){std::cout<<"pthread create error\n";return -1;}}for(i = 0;i < 4; i++){ret = pthread_create(&ptid[i],NULL,thr_productor,(void *)&q);if(ret != 0){std::cout<<"pthread create error\n";return -1;}}for(i = 0;i < 4; i++){pthread_join(ctid[i],NULL);} for(i = 0; i < 4;i++){pthread_join(ptid[i],NULL);}return 0;
}

在这里插入图片描述

信号量

进化版的互斥锁(1–>N)
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办 法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导 致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

计数器+等待队列+等待与唤醒功能
  1. 通过自身的计数器实现条件判断,当前条件满足时则直接返回并且计数-1.当条件并不满足时则阻塞
  2. 当产生资源后,通过信号量的唤醒功能唤醒等待并且计数+1

信号量和条件变量实现同步的区别

  1. 信号量的条件判断由自身来完成,而条件变量的条件判断由用户完成
  2. 信号量并不搭配互斥锁使用,而条件变量需要搭配互斥锁一起使用保护条件的改变

sem_init 函数

初始化一个信号量
int sem_init(sem_t *sem,int pshared,unsigned int value);
参 1:sem 信号量
参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
参 3:value 指定信号量初值

sem_destroy 函数

销毁一个信号量

int	sem_destroy(sem_t	*sem);

sem_wait 函数

给信号量加锁 ,对计数进行判断,计数<=0则阻塞;否则立即返回流程继续,计数-1

int	sem_wait(sem_t	*sem);

sem_post 函数

给信号量解锁 ,对计数进行+1,并且唤醒等到的线程

 int	sem_post(sem_t	*sem);

sem_trywait 函数

尝试对信号量加锁
(与 sem_wait 的区别类比 lock 和 trylock)

 int	sem_trywait(sem_t	*sem);

sem_timedwait 函数

限时尝试对信号量加锁

int	sem_timedwait(sem_t	*sem,const	struct	timespec	*abs_timeout); 

参 2:abs_timeout 采用的是绝对时间。
定时 1 秒:

time_tcur=time(NULL); 获取当前时间。 
structtimespect; 定义 timespec 结构体变量 t 
t.tv_sec=cur+1; 定时 1 秒 
t.tv_nsec=t.tv_sec+100; 
sem_timedwait(&sem,&t); 传参

使用信号量实现生产者与消费者模型

在这里插入图片描述

/*使用信号量实现生产者与消费者模型**/#include<iostream>
#include<queue>
#include<pthread.h>
#include<semaphore.h>class RingQueue
{public:RingQueue(int cap = 10):_capacity(cap),_queue(cap){//1.信号量变量//2.参数取值    0:用于线程间同步与互斥//              非0:用于进程间同步与互斥//3.信号量初值sem_init(&_sem_lock,0,1);//互斥锁初始值只给1sem_init(&_sem_data,0,0);//初始数据资源数据为0sem_init(&_sem_space,0,cap);//初始空闲空间计数}   ~RingQueue(){sem_destroy(&_sem_lock);sem_destroy(&_sem_data);sem_destroy(&_sem_space);}   void QueuePush(int data){// ProWait();//空闲空间计数判断是否有空闲空间,若有返回,否则等待// 因为已经通过_sem_space的空闲空间计数知道是否有空闲空间sem_wait(&_sem_space);//添加数据之后,空闲空间计数-1sem_wait(&_sem_lock);//锁计数初始为1,一旦进入-1加锁_queue[_step_write]=data;                                                                                                                                                                            _step_write = ( _step_write + 1) % _capacity;sem_post(&_sem_lock);//数据添加完毕后解锁,数据资源计数+1sem_post(&_sem_data);//数据添加完毕后,数据资源计数+1//ConWakeUp();}   void QueuePop(int *data){sem_wait(&_sem_data);//取数据的时候,数据资源计数-1sem_wait(&_sem_lock);//锁最好仅仅保护临界区*data = _queue[_step_read];_step_read = (_step_read + 1) % _capacity;sem_post(&_sem_lock);sem_post(&_sem_space);//取数据之后,空闲空间计数+!}private:std::vector<int>_queue;int _capacity; //队列最大数量int _step_write;//当前写到哪里的下标int _step_read;//当前读到哪里了的下标sem_t _sem_lock;//实现互斥锁sem_t _sem_space;//空闲空间计数sem_t _sem_data;//数据资源计数/*//队列加锁void QueueLock(){pthread_mutex_lock(&_mutex);}//队列解锁void QueueUnLock(){pthread_mutex_unlock(&_mutex);}*/  
};
void *thr_productor(void *arg){                                                                                                                                                                                  RingQueue *q = (RingQueue*)arg;int i=0;while(1){q->QueuePush(i);std::cout<<"thread:"<<pthread_self()<<"put data"<<i++<<"\n";}return NULL;
}void *thr_consumer(void *arg){RingQueue *q = (RingQueue*)arg;while(1){int data;q->QueuePop(&data);std::cout<<"thread:"<<pthread_self()<<"get data"<<data<<"\n";}return NULL;
}int main(int argc,char *argv[])
{RingQueue q;pthread_t ptid,ctid[4];int i ,ret;ret = pthread_create(&ptid,NULL,thr_productor,(void *)&q);if(ret != 0){std::cout<<"thread create error\n";return -1;}for(i = 0;i < 4;i++){ret = pthread_create(&ctid[i],NULL,thr_consumer,(void *)&q);if(ret != 0){std::cout<<"thread create error\n";return -1;}}for(i = 0; i < 4; i++){pthread_join(ctid[i],NULL);}pthread_join(ptid,NULL);return 0;
}

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

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

相关文章

Linux系统编程---18(线程池相关概念及其实现)

线程池 概念&#xff1a; 一堆线程任务队列 作用 避免大量线程频繁的创建/销毁时间成本避免瞬间大量线程创建耗尽资源&#xff0c;程序崩溃危险 实现 创建固定数量的线程创建一个线程安全的任务队列 一种线程使用模式。 线程过多会带来调度开销&#xff0c;进而影响缓…

设计模式--1(设计模式基础,设计模式基本原则,设计模式分类)

设计模式基础 模式 在一定环境中解决某一问题的方案&#xff0c;包括三个基本元素–问题&#xff0c;解决方案和环境。大白话&#xff1a;在一定环境下&#xff0c;用固定套路解决问题。 设计模式 是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使…

设计模式----2(简单工厂模式的概念,简单工厂模式的实现,简单工厂模式的优缺点)

简单工厂模式 简单工厂模式的概念 简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式。通过专门定义一个类来负 责创建其他类的实例&#xff0c;被创建的实例通常都具有共同的父类。 具体分类 工厂&#xff08;Creator&#xff09;角色 简单工厂模式的核心&#xff0…

设计模式---3(工厂方法模式的概念,工厂方法模式的实现,工厂方法模式和简单工厂模式比较)

工厂方法模式 概念 工厂方法模式同样属于类的创建型模式又被称为多态工厂模式 。 工厂方法模式的意义 定义一个创建产品对象的工厂接口&#xff0c;将实际创建工作推迟到子类当中。 核心工厂类不再负责产品的创建&#xff0c;这样核心类成为一个抽象工厂角色&#xff0c;仅…

设计模式---4(抽象工厂模式的概念,产品组和产品等级的概念,抽象工厂模式的实现)

抽象工厂模式 抽象工厂模式的概念 抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向 客户端提供一个接口&#xff0c;使得客户端在不必指定产品的具体类型的情况下&#xff0c;能够创建多个产品 族的产品对象。 抽象工厂的角色及其职责 抽象工…

1.c++中初始化列表和构造函数初始化的区别是什么?2.类的成员变量的初始化顺序是按照声明顺序吗?

初始化列表和构造函数初始化的区别是什么&#xff1f; 初始化和赋值对内置类型的成员没有太大的区别&#xff0c;在成员初始化列表和构造函数体内进行&#xff0c;在性能和结果上都是一样的。只有一些需要注意的事项 初始化列表一般情况如下&#xff1a; Date(int year, int …

设计模式---5(建造者模式的概念及其实现,建造者模式的角色与职责,建造者模式和工厂模式的区别)

建造者模式 建造者模式的概念 Builder 模式也叫建造者模式或者生成器模式&#xff0c;是由 GoF 提出的 23 种设计模式中的一种。 Builder 模式是一种对象创建型模式之一&#xff0c;用来隐藏复合对象的创建过程&#xff0c;它把复合对象的 创建过程加以抽象&#xff0c;通过子…

system阻塞SIGCHLD信号原因

system阻塞SIGCHLD信号原因 标签&#xff1a; c 2014-11-08 11:58 198人阅读 评论(0) 收藏 举报 分类&#xff1a; linux编程&#xff08;1&#xff09; 代码1&#xff1a;APUE10.18节的system函数源代码 int system(const char *cmdstring) /* with appropriate signal ha…

设计模式6---(单例模式的概念及其实现(懒汉式和饿汉式),线程安全)

单例模式 单例模式的概念 单例模式是一种对象创建型模式&#xff0c;使用单例模式&#xff0c;可以保证为一个类只生成唯一的实例对象。也就是说&#xff0c;在整个程序空间中&#xff0c;该类只存在一个实例对象。 GoF 对单例模式的定义是&#xff1a;保证一个类、只有一个实…

套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题,TCP套接字多进程版本,TCP套接字多线程版本)

TCP模型创建流程图 TCP套接字编程中的接口 socket 函数 #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生socket的协议&#xff0c;使用TCP或UDP来传输&…

Linux中netstat工具详解

简介 Netstat 命令用于显示各种网络相关信息&#xff0c;如网络连接&#xff0c;路由表&#xff0c;接口状态 (Interface Statistics)&#xff0c;masquerade 连接&#xff0c;多播成员 (Multicast Memberships) 等等。 常见参数 -a (all)显示所有选项&#xff0c;默认不显示…

网络基础 2-1(应用层,HTTP三点注意,HTTP协议格式, 最简单的HTTP服务器)

应用层 应用层 负责应用程序之间的数据沟通-----协议都是用户自己定的 自定制协议&#xff1a; 结构化数据传输 序列化&#xff1a; 将数据对象以指定的协议&#xff08;数据格式&#xff09;进行可用于持久化存储或者数据传输时的数据组织 例如在分布式的系统中&#xf…

网络基础2-2(传输层,端口,详谈UDP)

传输层 负责数据能够从发送端传输接收端. 端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序;在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n查看);一个端口只能被一个…

网络基础2-3(TCP协议,三次握手,四次挥手,TIME_WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动窗口,流量控制,快速重传,拥塞窗口,延迟应答,捎带应答,粘包问题)

TCP协议 TCP协议概念 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制 TCP协议格式 1. 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 2. 32位序号/32位确认号: 后面详细讲; 3. 4位TCP报头长度: 表示该…

字符串题目 1 --------判断两个字符串是否为旋转词

题目描述 如果一个字符串为str&#xff0c;把字符串的前面任意部分挪到后面形成的字符串交str的旋转词。比如str“12345”&#xff0c;str的旋转串有“12345”、“45123”等等。给定两个字符串&#xff0c;判断是否为旋转词。 输入描述: 输出包含三行&#xff0c;第一个两个…

字符串题目---2判断两个字符串是否为变形词

题目描述 给定两个字符串str1和str2&#xff0c;如果str1和str2中出现的字符种类出现的一样且每种字符出现的次数也一样&#xff0c;那么str1和str2互为变形词。请判断str1和str2是否为变形词 输入描述: 输入包括3行&#xff0c;第一行包含两个整数n&#xff0c;m(1 \leq n,…

设计模式7----代理模式

代理模式 概念 Proxy 模式又叫做代理模式&#xff0c;是结构型的设计模式之一&#xff0c;它可以为其他对象提供一 种代理&#xff08;Proxy&#xff09;以控制对这个对象的访问。 所谓代理&#xff0c;是指具有与代理元&#xff08;被代理的对象&#xff09;具有相同的接口的…

网络基础3-1(细谈IP协议头, 网络层,子网划分,路由选择,数据链路层,以太网帧格式,MAC地址,再谈ARP协议)

IP协议 IP协议头格式 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是44位头部长度(header length): IP头部的长度是多少个。32bit, 也就是 length * 4 的字节数. 4bit表示大 的数字是15, 因此IP头部大长度是60字节8位服务类型(Type Of Service): 3位优先权字段(已…

网络中典型协议--(DNS,输入url后, 发生的事情. ,ICMP,NAT)

DNS&#xff08;Domain Name System&#xff09; DNS是一整套从域名映射到IP的系统 域名服务器发展背景 TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序. 但是IP地址不方便记忆. 于是人们发明了一种叫主机名的东西, 是一个字符串, 并且使用hosts文件来描述主机…

高级IO--1 ---(五种典型IO,阻塞IO,非阻塞IO,信号驱动IO,异步IO, IO多路转接)

高级IO&#xff1a; 五种典型IO&#xff1a; 阻塞IO/非阻塞IO/信号驱动IO/异步IO/IO多路转接 IO多路转接模型&#xff1a;select/poll/epoll 五种典型IO 阻塞IO IO操作的流程&#xff1a;等待IO操作条件具备&#xff0c;然后进行数据拷贝 为了完成IO操作发起调用&#xff…