【问题标题】:Updating a global variable in an asynchronous I/O callback在异步 I/O 回调中更新全局变量
【发布时间】:2021-07-22 20:44:27
【问题描述】:

希望包含的 C 代码不要太长。我试图将它削减到最低限度以显示我遇到的问题。我正在使用异步 I/O 写入少量数据。主要代码调用aio_write,然后等待I/O回调设置一个全局变量再返回。我知道这不是通常的做事方式,但我正在尝试测试与给我带来真正问题的更大代码相关的内容。

这段代码在 Cray 或 GNU 下编译和运行都很好。

在 Intel 下,它可以通过优化“-O1”编译和运行。但是具有任何更高优化级别的英特尔构建会挂在等待循环中。回调确实设置了全局变量,但主代码从未看到它发生。

例如,在 GNU 下,输出类似于,

> head -n 32 /dev/urandom > random_stuff.dat   # put random data into test file
> ./write_only 10    # run the code; read and write ten doubles from the input file
reader read 5.991429e-16
writer wrote 80 bytes
writer received 5.991429e-16
writer thinks no_more_writes is 1
ran in 7.650000e-04 sec

但是,使用 Intel(以“-O2”或更高版本编译),我得到

reader read 5.991429e-16
writer wrote 80 bytes
writer received 5.991429e-16
writer thinks no_more_writes is 1
main code still waiting
main code still waiting
main code still waiting
main code still waiting
....

我对异步 I/O 比较陌生。我知道全局变量存在问题,例如竞争条件。但是这个简单的代码不应该出现这个问题。知道什么是错的,为什么它只对优化的英特尔构建是错误的? (顺便说一句,如果我用基于 aio_error() 的通常等待替换全局变量等待,所有编译器和所有优化级别的一切都正常。我认为,不过,对于我正在处理的更大的真实代码,我需要一些东西更像我在这里所做的。)

代码如下:

#include <stdio.h>
#include <stdlib.h>

#include <aio.h>

#include <fcntl.h>
#include <unistd.h>

#include<strings.h>

/* for EINPROGRESS value: */
#include <errno.h>

/* for timing */
#include <sys/time.h>

void writer(sigval_t sigval);

int chunksize, numelems;
double *alldata;

int no_more_writes=0;


int main(int argc, char **argv) {

   struct aiocb aiowriter;
   int fdin, fdout;

   struct timeval start_time, end_time;
   long int time_usec;
   double time_sec;

   char *infile = "random_stuff.dat";

   if (argc < 2) {
           printf("./write_only  numelems\n");
           return 1;
   }

   numelems = atoi(argv[1]);
   chunksize = numelems * sizeof(double);

// allocate alldata
   alldata = (double*)calloc(numelems, sizeof(double));

// open up input file and read
   fdin = open(infile, O_RDONLY);
   read(fdin, alldata, chunksize);
   close(fdin);
   printf("reader read %e \n", alldata[0]);

// writing

// open up output file
   fdout = open("write_out.dat", O_WRONLY | O_CREAT, S_IRUSR | S_IRGRP);

// Check is opening of output file was successful:
   if (fdout == -1) {
      printf("cannot open 'write_out.dat'\n");
      if (errno == EACCES || errno == EEXIST) {
         printf("file aleady exists\n");
      } else {
         printf("errno is %d\n", errno);
      }
      return errno;
   }

// bzero out aiowriter
   bzero((char*)&aiowriter, sizeof(struct aiocb));

// set writer file desc. and other related info
   aiowriter.aio_fildes = fdout;
   aiowriter.aio_offset = 0;
   aiowriter.aio_nbytes = chunksize;
// set writer buffer
   aiowriter.aio_buf = (char*)alldata;
// set writer callback info
   aiowriter.aio_sigevent.sigev_notify = SIGEV_THREAD;
   aiowriter.aio_sigevent.sigev_notify_function = writer;
   aiowriter.aio_sigevent.sigev_notify_attributes = NULL;
   aiowriter.aio_sigevent.sigev_value.sival_ptr = &aiowriter;

// timing
   gettimeofday(&start_time, NULL);

// the write
   aio_write(&aiowriter);

   int debugindex=0;
// wait for final writing to finish
   while (no_more_writes != 1) {
           usleep(100);
           if (debugindex > 10000) {
                   printf("main code still waiting\n");
                   debugindex=1;
           }
           debugindex++;
   }
// alternative wait loop:
   //while (aio_error(&aiowriter) == EINPROGRESS) {}

// timing
   gettimeofday(&end_time, NULL);
   time_usec = 1000000*(end_time.tv_sec - start_time.tv_sec) +
                        (end_time.tv_usec - start_time.tv_usec);
   time_sec = time_usec / 1000000.;
   printf("ran in %e sec\n", time_sec);

   free(alldata);

}



// write function
void writer(sigval_t sigval) {

   int write_error;

// inputs: the AIO writing object
   struct aiocb *writing;
   writing = (struct aiocb *)sigval.sival_ptr;

// check amount of data written
   write_error =  aio_return(writing);

   printf("writer wrote %d bytes\n", write_error);
   printf("writer received %e\n", alldata[0]);

   close(writing->aio_fildes);

   no_more_writes = 1;
   printf("writer thinks no_more_writes is %d\n",
                   no_more_writes);


   return;

}

【问题讨论】:

    标签: c asynchronous callback io


    【解决方案1】:

    您需要将全局变量设置为volatile,否则编译器可以优化冗余负载,因为它不会知道变量是从外部更改的。

    volatile int no_more_writes = 0;
    

    【讨论】:

    • 谢谢!做到了。问题,如果可以的话:在我真正正在处理的大型代码中,有几个由 I/O 回调更新的全局变量。他们似乎没有遇到任何麻烦。是否有一些关于哪些全局变量应该是易变的而哪些不是的规则?或者他们都应该是不稳定的,而我刚刚和其他人一起走运了?再次感谢。
    • 那是幸运的。由于情况不同,编译器可能会重新加载代码,但为了安全起见,您还应该声明它们volatile。如果不是,那么稍后的一些代码更改可能会导致不同的行为。
    • 如您所见,在不同的优化级别上它的行为可能不同,因为它取决于编译器的实现。在最坏的情况下,如果编译器确定可观察行为没有变化,它甚至可以丢弃整个函数。 volatile 关键字告诉编译器它不能对它的当前值做出任何假设。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多