【问题标题】:OpenMP cancel sectionOpenMP 取消部分
【发布时间】:2017-04-19 13:15:21
【问题描述】:

我在终止 C 程序中的部分时遇到问题。在一个线程中捕获SIGINT 信号后,我想退出所有线程,但我真的不知道如何退出,因为这些循环中有无限循环。程序等待来自服务器或标准输入的输入。所以我使用了信号处理程序。

我真的不知道我这样做是否正确,我也不太了解 OpenMP 中的取消是如何工作的。我没有找到合适的教程或讲座。

我的任务是在捕捉到SIGINT 信号后,终止程序。但是当我在处理程序中使用exit() 时,它显然会留下未释放的内存。我很乐意为您提供任何建议,谢谢。

#pragma omp parallel num_threads(2)
{
    #pragma omp sections
    {
        #pragma omp section
        {
            void intHandler(int dummy) 
            {
                char * welcome1 = calloc(strlen(username)+14,sizeof(char));
                strcat(welcome1,username);
                strcat(welcome1," logged out\r\n");
                if(send(client_socket,welcome1,strlen(welcome1),0) < 0)
                {
                    callError("ERROR: cannot send socked");
                }
                free(welcome1);
                #pragma omp cancel section
            }
            signal(SIGINT, intHandler);
            int i = 0, j = 1;
            while(1)
            {
                str = (char*)malloc(sizeof(char));
                while((c = getc(stdin)) != '\n')
                {

                    str = (char*)realloc(str, j * sizeof(char));
                    str[i] = c;
                    i++;
                    j++;
                }
                str = (char*)realloc(str, j * sizeof(char));
                str[i] = '\0';
                if(strlen(str)!=0)
                {
                    bufferIn = message(username,str);
                    if(send(client_socket,bufferIn,strlen(bufferIn),0) < 0)
                    {
                        callError("ERROR: cannot send socked");
                    }
                    free(bufferIn);
                }
                free(str); i = 0; j = 1; 
            }
        #pragma omp cancellation point section
        }
        #pragma omp section
        {
            void intHandler(int dummy) 
            {
                char * welcome1 = calloc(strlen(username)+14,sizeof(char));
                strcat(welcome1,username);
                strcat(welcome1," logged out\r\n");
                if(send(client_socket,welcome1,strlen(welcome1),0) < 0)
                {
                    callError("ERROR: cannot send socked");
                }
                free(welcome1);
                #pragma omp cancel section
            }
            signal(SIGINT, intHandler);
            char buffer[4096];
            ssize_t length;
            int received = 0;
            int data_cap = 4096;
            while(1)
            {
                data = calloc(BUFFER_LEN,sizeof(char));
                while ((length = read(client_socket, buffer, BUFFER_LEN-1)) > 0)
                {
                    received += length;
                    buffer[length] = '\0';
                    if (received > data_cap)
                    {
                         data = realloc(data,sizeof(char) * data_cap * 2);
                         data_cap = data_cap * 2;
                    }
                    strcat(data, buffer); 
                    if(!isEnough(data))
                    {
                        break;
                    }
                }
                printf("%s", data);
                free(data); bzero(buffer,BUFFER_LEN); data_cap = 4096; received = 0; length = 0;
            }
        #pragma omp cancellation point section
        }
    }

} 

【问题讨论】:

  • "但是当我在处理程序中使用 exit() 时,它显然会留下未释放的内存。"嗯,这根本没有意义。一旦进程退出,它的所有内存都会被操作系统释放。 (当然,除非这里有您没有提及的共享内存段)。
  • 我知道,但是将内存泄漏处理留给操作系统是不好的做法,就我的目的而言,它真的很糟糕,因为我在非常有限的机器上运行这个程序
  • @kakra “将内存泄漏处理留给操作系统是不好的做法”。我不同意。当整个地址空间即将被拆除时,使用“空闲”将分配的存储返回堆是浪费您的精力和 CPU 时间。当操作系统取消映射页面时,堆是否在其空闲列表中具有数据对操作系统没有任何影响,因此您只是通过调用 free 来浪费 CPU 时间。

标签: c multithreading openmp


【解决方案1】:

这实际上非常复杂,但让我们从简单开始。

  • #pragma omp cancellation point sections / #pragma omp cancel sections(请注意)。

  • 您不能跨函数边界使用#pragma omp cancel1

假设您可以通过这种方式使用取消,仅在特定取消点检查取消。因此,在阻塞 readgetc 期间,您的线程不会被取消中断。

信号处理

信号处理程序是按进程设置的,信号在哪个线程结束并不确定。您不应该尝试从多个线程同时调用signalsignal 的一些实现甚至像多线程程序一样。相反,您应该使用sigaction,但在产生工作线程之前仍然设置一次全局信号处理程序。

在信号处理程序中允许您执行的操作有一定的限制。基本上你不能访问任何非volatile sig_atomic_t 类型的全局变量并且只调用async-signal-safe functions。您在信号处理程序的每一行都违反了这一点。

特别是,您调用send(client_socket),而同一线程可能只是在调用read(client_socket)2 或另一个线程同时调用read(client_socket) 时被中断。不知道更糟的是什么,但即使 send 本身是 async-signal-safe,我敢打赌,它在建议的方式中并不安全。

您知道,在进程结束时拥有一些可访问的内存绝对是您最不重要的问题。你甚至不能打电话给exit(不过你可以打电话给_exit)。

通常的出路是设置一个volatile sig_atomic_t 类型的全局取消标志,并在信号处理程序中设置它,并在工作循环中检查它。这也应该适用于 OpenMP 部分/线程,但我建议添加 #pragma omp atomic read/write seq_cst 以对标志进行任何读/写。这似乎是多余的,但我相当确定volatile sig_atomic_t 只保证关于信号中断的原子性,而不是多线程,特别是存储可见性。不幸的是,您仍然遇到readgetc 阻塞的问题...

解决方案草图

因此您必须使用某种机制使getc 非阻塞或添加超时以使您的线程有机会检查取消标志。

poll可以给你一个更优雅的出路。您可以将readgetc 的阻塞部分替换为poll - 但是请记住,这会迫使您将stdin 专门用作文件描述符,而不是FILE*。在准备过程中,您为每个部分制作一个管道,其输出包含在相应的poll 中。在仅在生成管道后设置的信号处理程序中,您写入这些管道,指示线程应该关闭。如果 poll 显示那些特定 stop-pipe-fds 的活动,则退出循环,并在适当的线程中或并行区域之后进行清理。根据需要应用同步。

很抱歉给您带来坏消息。它很复杂,没有简单、正确、可复制粘贴的解决方案。无论如何,在这里使用 OpenMP 取消是不正确的。

1:标准对此并不十分明确,它要求:

在执行可能会被取消的构造期间,线程不得遇到孤立的取消点。也就是说,取消点只能在该构造内遇到,而不能在其区域的其他地方遇到。

但是,由于取消区域本身就是隐式取消点,我想有人可能暗示取消构造必须始终在显式并行区域的词法范围内。无论如何,gcc 不会让您使用取消构造编译像信号处理程序这样的函数。

2:这实际上很好,但是您至少必须手动恢复errno,因为信号处理程序中的write 会覆盖它。 read 应该很方便地返回 EINTR

【讨论】:

  • 非常感谢它帮助了我:)
  • @krakra 很高兴听到这个消息 :-),我担心我的长文会阻止您尝试正确解决这个问题。随意发布您基于此答案取得的任何进一步进展作为单独的答案,或者如果它基本上解决了您的问题,请接受我的。
  • 我做了你告诉我的我摆脱了线程中的处理程序和你告诉我我的代码中有其他坏东西。我真的不知道该怎么做,所以我很高兴你的回答,我将使用 select() 函数和阻塞操作来处理信号和退出线程
  • 我建议使用poll 而不是select。它基本上做同样的事情,但界面更好,危险性更小。
  • 最后一个问题,我必须对部分进行一些更正吗?如果我想保护它免受并行编程可能存在的数据竞争或其他问题的影响?或者它自己处理这个问题的openMP
猜你喜欢
  • 1970-01-01
  • 2014-04-20
  • 2018-03-10
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多