【问题标题】:OpenMP not waiting all threads finish before end C programOpenMP 不等待所有线程在结束 C 程序之前完成
【发布时间】:2020-11-09 04:05:00
【问题描述】:

我有以下问题:我的 C 程序必须计算文本文件中单词列表的出现次数。

我为此使用 OpenMP,并且该程序理论上具有正确的逻辑。当我将一些printfs 放入For Loop 中时,程序的结果是正确的并且总是相同的。

当我删除printfs 时,结果不正确,并且每次执行它的值都会改变。鉴于这种情况,我认为原因与执行时间有关。使用printfs 会增加执行时间,因此有时间完成对所有线程的计数,并且程序可以正常工作。如果没有prinfts,执行时间会呈指数级减少(0.000893 毫秒),没有时间完成所有线程/计算,因此程序会为每次执行打印不同的结果。

并行化代码如下:

#pragma omp parallel for schedule(dynamic) num_threads(threadNumber) private(word, wordExists) shared(keyWordsOcurrences)
          for (line = 0; line < NUM_LINES; line++)
            {
                // divides the line into words separated by space
                word = strtok(lines[line], " ");
                while (word != NULL)
                {
                    // checks if the word being read is one of the monitored words
                    wordExists = checkWordOcurrences(word);
                    if (wordExists)
                    {
                        #pragma omp critical
                        keyWordsOcurrences[wordExists - 1] += 1;
                    }
                    word = strtok(NULL, " ");
                }
            }

调用的 checkWordOcurrences 函数是我放置 printf 的地方,它负责使我的代码在每次执行中都能正常工作(增加执行时间)。

int checkWordOcurrences(char *word)
{
    int res = 0;
    int i;

    for (i = 0; i < QTD_WORDS; i++)
    {
        // **this is the almighty Printf that makes everything work properly, and without it things stop working :(**
        printf("palavra %d %s - palavra 2 %s \n", i, keyWords[i], word);
        // compares current word with monitored words
        if (!strcmp(keyWords[i], word))
        {
            // if it's monitored word, returns its index (+1 because the first word has index 0 and the return type is checked as true or false)
            res = i + 1;
        }
    }

    // returns word index or 0, if current word is not monitored
    return res;
}

有人可以向我解释可能发生的情况和/或如何解决它吗?

【问题讨论】:

    标签: c multithreading openmp


    【解决方案1】:

    在 OpenMP for 构造的末尾和每个并行区域的末尾都有一个隐式屏障,因此程序不可能在所有线程完成分配的工作之前完成。

    该问题很可能是由使用strtok 引起的。它不是线程安全的函数,因为搜索点的位置存储在 C 库内部。当一个线程正在对某事物进行标记而另一个线程调用strtok(lines[line], " "); 时,这会覆盖指向正在搜索的字符串的指针,现在所有其他调用strtok(NULL, " "); 的线程正在标记新设置的字符串,而不是它们所在的字符串之前标记化的中间。这是数据竞赛的经典案例。

    解决方案是改用strtok_r

    #pragma omp parallel for schedule(dynamic) num_threads(threadNumber) private(word, wordExists) shared(keyWordsOcurrences)
              for (line = 0; line < NUM_LINES; line++)
                {
                    char *saveptr;
                    // divides the line into words separated by space
                    word = strtok_r(lines[line], " ", &saveptr);
                    while (word != NULL)
                    {
                        // checks if the word being read is one of the monitored words
                        wordExists = checkWordOcurrences(word);
                        if (wordExists)
                        {
                            #pragma omp critical
                            keyWordsOcurrences[wordExists - 1] += 1;
                        }
                        word = strtok_r(NULL, " ", &saveptr);
                    }
                }
    

    在单独的帐户中,critical 是一个非常重量级的同步构造,使用锁实现。像keyWordsOcurrences[wordExists - 1] += 1; 这样的简单增量可以用原子更新来保护,这样更快:

    if (wordExists)
    {
        #pragma omp atomic update
        keyWordsOcurrences[wordExists - 1] += 1;
    }
    

    如果QTD_WORDS 不是一个很大的数字,你也可以使用数组缩减:

    #pragma omp parallel for schedule(dynamic) num_threads(threadNumber) \
                             private(word, wordExists) \
                             reduction(+:keyWordsOcurrences[0:QTD_WORDS])
              for (line = 0; line < NUM_LINES; line++)
                {
                    char *saveptr;
                    // divides the line into words separated by space
                    word = strtok_r(lines[line], " ", &saveptr);
                    while (word != NULL)
                    {
                        // checks if the word being read is one of the monitored words
                        wordExists = checkWordOcurrences(word);
                        if (wordExists)
                        {
                            keyWordsOcurrences[wordExists - 1] += 1;
                        }
                        word = strtok_r(NULL, " ", &saveptr);
                    }
                }
    

    C 和 C++ 的数组缩减是一项相对较新的 OpenMP 功能,需要支持 OpenMP 4.5 的编译器。对于较旧的编译器,可以手动完成,但这超出了原始问题的范围。

    【讨论】:

    • 作为改进的建议,将删除该关键点,以实现更细粒度的锁定或使用数据复制。
    • @dreamcrash 我之前犹豫不决,因为它超出了原始问题的范围,并引入了人们经常尝试在错误情况下应用的概念。
    • 是的,你实际上是对的,但是现在答案看起来很完美。
    • 谢谢您,先生,您的解决方案有效,通过阅读您的回答,我完全理解我做错了什么。
    猜你喜欢
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多