我了解一旦文件读取完成,就会调用回调函数。但究竟会发生什么?
发生的情况是,当 IO 完成时,它“表现得好像”它启动了一个新线程(类似于调用 pthread_create(&ignored, NULL, finish_aio, &info_on_file))。
aio_error(&my_aiocb)什么时候停止返回EINPROGRESS?
我希望aio_error(&my_aiocb) 在 IO 完成后立即停止返回 EINPROGRESS,然后系统(可能是标准库)要么开始创建一个新线程来调用 finish_aio(),要么“解除阻塞”一个“以前在您不知情的情况下创建”线程。但是,我不认为任何地方都记录了确切的顺序(“定义的实现”),因为无论如何从 finish_aio() 以外的任何地方调用 aio_error(&my_aiocb) 都没有多大意义。
更具体地说;如果您正在使用轮询 (my_aiocb.aio_sigevent.sigev_notify = SIGEV_NONE),那么您将自己反复检查 aio_error(&my_aiocb),并且您不在乎在此之前或之后是否收到通知,因为您根本没有收到通知;如果您不使用轮询,您将等到(通过新线程或信号)通知您有理由检查aio_error(&my_aiocb)。
换句话说,您的finish_aio() 看起来更像这样:
void finish_aio(sigval_t sigval) {
struct aiocb * my_aiocb = (struct aiocb *)sigval;
int status;
status = aio_error(&my_aiocb);
/* Figure out what to do (handle the error or handle the file's data) */
.. 对于main(),while(aio_error(&my_aiocb) == EINPROGRESS)(这可能会浪费大量的 CPU 时间)将被删除和/或可能被其他东西替换(例如,pthread_cond_wait() 等到代码finish_aio() 执行 pthread_cond_signal() 告诉主线程它可以继续)。
为了理解这一点,让我们看一下纯轮询的样子:
int main() {
struct aiocb my_aiocb;
int aio_return;
...
//Open file, take care of any other prelims, then
//Fill in file-specific info for my_aiocb, then
my_aiocb.aio_sigevent.sigev_notify = SIGEV_NONE; /* CHANGED! */
// my_aiocb.aio_sigevent.sigev_notify_function = finish_aio;
// my_aiocb.aio_sigevent.sigev_notify_attributes = NULL;
// my_aiocb.aio_sigevent.sigev_value.sival_ptr = &info_on_file;
// then read the file:
aio_return = aio_read(&my_aiocb);
// do stuff that doesn't need data that is being read ...
// then block execution until read is complete:
while(aio_error(&my_aiocb) == EINPROGRESS) {}
finish_aio(sigval_t sigval); /* ADDED! */
}
在这种情况下,它的行为几乎与您的原始代码相同,只是没有额外的线程(而且您不必关心“不存在的线程”是在 aio_error(&my_aiocb) 返回值之前还是之后启动的除了EINPROGRESS)。
纯轮询的问题在于,while(aio_error(&my_aiocb) == EINPROGRESS) 可能会浪费大量 CPU 时间不断检查何时什么都没有发生。
使用my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD 的主要目的是避免在没有任何变化的情况下浪费大量的 CPU 时间轮询(不要忘记在某些情况下,像这样浪费 CPU 时间轮询会阻止其他线程,包括 finish_aio() 线程,从获取 CPU 时间)。换句话说,您想删除while(aio_error(&my_aiocb) == EINPROGRESS) 循环,所以您使用了SIGEV_THREAD,以便您可以删除该轮询循环。
新的问题是(如果主线程必须等到数据准备好)你需要一些其他的方式让主线程等到数据准备好。但是,通常您真正关心的不是“aio_read() 完成”,而是其他东西。例如,原始文件数据可能是文本文件中的一堆值(如“12、34、56、78”),您想要解析该数据并创建一个整数数组,并想要通知主线程整数数组已准备就绪(如果您开始解析文件的数据,不想通知主线程)。可能是这样的:
int parsed_file_result = 0;
void finish_aio(sigval_t sigval) {
struct aiocb * my_aiocb = (struct aiocb *)sigval;
int status;
status = aio_error(&my_aiocb);
close(my_aiocb->aio_fildes);
if(status == 0) {
/* Read was successful */
parsed_file_result = parse_file_data(); /* Create the array of integers */
} else {
/* Read failed, handle the error somehow */
parsed_file_result = -1; /* Tell main thread we failed to create the array of integers */
}
/* Tell the main thread it can continue somehow */
}
告诉主线程它可以继续(在finish_aio() 的末尾)的最好方法之一是使用pthread 条件变量(例如pthread_cond_signal() 在finish_aio() 的末尾调用;在pthread_cond_wait() 中主线程)。在这种情况下,主线程将简单地阻塞(内核/调度程序在调用 pthread_cond_signal() 之前不会给它任何 CPU 时间),因此不会浪费 CPU 时间轮询。
遗憾的是,pthread 条件变量并非微不足道(它们需要互斥体、初始化等),并且在这里教授/展示它们的使用与原始主题有点太远了。幸运的是;在其他地方找到一个好的教程应该不会有太大的麻烦。
重要的是,如果你使用了SIGEV_THREAD(这样你就可以删除那个糟糕的while(aio_error(&my_aiocb) == EINPROGRESS) 轮询循环),在finish_aio() 已经启动之前,你没有理由调用aio_error(&my_aiocb) ;并且没有理由关心 aio_error(&my_aiocb) 在 finish_aio() 启动之前是否已更改(或不更改)。