【问题标题】:Data races when using mutexes使用互斥锁时的数据竞争
【发布时间】:2013-03-08 10:42:48
【问题描述】:

我正在做一个涉及线程和互斥锁的 C 小项目。我正在开发的程序在 bmp 法师上应用过滤器。该项目的目标是实现一个能够处理此命令行的程序:

$ ./filter -f filter1[,filter2[,...]] -t numThreads1[,numThreads2[,...]] input-folder output-folder

其中 -f 是我要应用的过滤器(“red”、“blue”、“green”、“grayscale”和“blur”),-t 是每个过滤器分配的线程数。

到目前为止一切都很好,除了模糊,我被困在数据竞赛中(或者,我认为是这样)。 模糊滤镜的工作方式如下:

/* Add a Gaussian blur to an image using
* this 3X3 matrix as weights matrix:
*   0.0  0.2  0.0
*   0.2  0.2  0.2
*   0.0  0.2  0.0
*
* If we consider the red component in this image
* (every element has a value between 0 and 255)
*
*   1  2  5  2  0  3
*      -------
*   3 |2  5  1| 6  0       0.0*2 + 0.2*5 + 0.0*1 +
*     |       |
*   4 |3  6  2| 1  4   ->  0.2*3 + 0.2*6 + 0.2*2 +   ->  3.2
*     |       |
*   0 |4  0  3| 4  2       0.0*4 + 0.2*0 + 0.0*3
*      -------
*   9  6  5  0  3  9
* 
* The new value of the pixel (3, 4) is round(3.2) = 3.
*
* If a pixel is outside the image, we increment the central pixel weight by 0.2
* So the new value of pixel (0, 0) is:
*   0.2 * 0 + 0.2 * 9 + 0.2 * 6 + 0.2 * 9 + 0.2 * 9 = 6.6 -> 7
*/

问题是,当我使用这个模糊过滤器在“棋盘”图像上运行我的程序时:

$ ./filter -f blur -t 8 chess.bmp chessBlur.bmp

我期待得到这个image,但我得到的是this(“断”行随机变化)

我正在使用互斥锁来锁定和解锁关键部分,但正如您所见,数据竞争仍然存在。 过滤器上只有两个词,我一次给每个线程一行,从底部开始向上。 我的 filter_blur 代码是:

int filter_blur(struct image *img, int nThread)
{
    int error = 0;
    int mod = img->height%nThread;
    if (mod > 0)
        mod = 1;

    pthread_t threads[nThread];
    pthread_mutex_t mutex;
    args arguments[nThread];

    struct image* img2 = (struct image*)malloc(sizeof(struct image));
    memcpy(img2,img,sizeof(struct image));

    error=pthread_mutex_init( &mutex, NULL);
    if(error!=0)
        err(error,"pthread_mutex_init");

    int i = 0;
    for (i=0; i<nThread; i++) {
        arguments[i].img2 = img2;
        arguments[i].mutex = &mutex;
    }

    int j = 0;
    for (i=0; i<(img->height)/nThread + mod; i++) {
        for (j=0; j<nThread; j++) {

            arguments[j].img = img; arguments[j].line = i*nThread + j;

            error=pthread_create(&threads[j],NULL,threadBlur,(void*)&arguments[j]);
            if(error!=0)
                err(error,"pthread_create");
        }
        for (j=0; j<nThread; j++) {
            error=pthread_join(threads[j],NULL);
            if(error!=0)
                err(error,"pthread_join");
        }
    }
    free(img2);
    return 0;
}

void* threadBlur(void* argument) {

    // unpacking arguments
    args* image = (args*)argument;
    struct image* img = image->img;
    struct image* img2 = image->img2;
    pthread_mutex_t* mutex = image->mutex;

    int error;
    int line = image->line;
    if (line < img->height) {
        int i;

        error=pthread_mutex_lock(mutex);
        if(error!=0)
            fprintf(stderr,"pthread_mutex_lock");

        for (i=0; i<img->width; i++) {
            img->pixels[line * img->width +i] = blur(img2,i,line);
        }

        error=pthread_mutex_unlock(mutex);
        if(error!=0)
            fprintf(stderr,"pthread_mutex_unlock");
    }
    pthread_exit(NULL);
}

struct pixel blur(struct image* img2, int x, int y) {
    double red = 0;
    double green = 0;
    double blue = 0;

    red=(double)img2->pixels[y * img2->width + x].r/5.0;
    green=(double)img2->pixels[y * img2->width + x].g/5.0;
    blue=(double)img2->pixels[y * img2->width + x].b/5.0;

    if (x != 0) {
        red+=(double)img2->pixels[y * img2->width + x - 1].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x - 1].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x - 1].b/5.0;
    } else {
        red+=(double)img2->pixels[y * img2->width + x].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x].b/5.0;
    }

    if (x != img2->width - 1) {
        red+=(double)img2->pixels[y * img2->width + x + 1].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x + 1].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x + 1].b/5.0;
    } else {
        red+=(double)img2->pixels[y * img2->width + x].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x].b/5.0;
    }

    if (y != 0) {
        red+=(double)img2->pixels[(y - 1) * img2->width + x].r/5.0;
        green+=(double)img2->pixels[(y - 1) * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[(y - 1) * img2->width + x].b/5.0;
    } else {
        red+=(double)img2->pixels[y * img2->width + x].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x].b/5.0;
    }

    if (y != img2->height - 1) {
        red+=(double)img2->pixels[(y + 1) * img2->width + x].r/5.0;
        green+=(double)img2->pixels[(y + 1) * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[(y + 1) * img2->width + x].b/5.0;
    } else {
        red+=(double)img2->pixels[y * img2->width + x].r/5.0;
        green+=(double)img2->pixels[y * img2->width + x].g/5.0;
        blue+=(double)img2->pixels[y * img2->width + x].b/5.0;
    }

    struct pixel pix = {(unsigned char)round(blue),(unsigned char)round(green),(unsigned char)round(red)};
    return pix;
}

编辑 1:

正如@job 正确猜测的那样,问题是由我的结构的 memcpy 引起的(结构被复制,但结构内的指针仍然指向原始结构元素)。 我还删除了互斥锁(它们只是在这里,因为我认为它们可以解决我的问题,对不起,我的错) 现在我的项目工作起来就像一个魅力(即使我们仍然可以讨论处理速度,以及使用线程的需要)。正如我所说,这是一个项目,一个针对我的 C 班的大学项目。目标是并行化我们的过滤器。所以需要线程。

谢谢!

【问题讨论】:

  • 用 valgrind 运行它。这应该可以帮助您识别可能受竞争条件影响的代码
  • 在我看来,所有这些线程只会让算法慢得多。所有实际工作都在互斥锁内完成,因此您将有一个线程工作,多个无用线程处于休眠状态。
  • 我认为,从一个内存区域读取数据并将模糊图像写入新位置是个好主意。
  • 什么是struct imagepixels 是数组还是指向动态分配内存的指针?如果是后者,那么img2 仍然会指向原始像素。
  • @jop 进行深度复制会使这段代码更加疯狂。最初只有一个数据块需要处理。然后,由于未知原因,OP 决定将块分成许多,许多线程处理效率低下。现在我们还应该添加复制和动态分配的开销吗? 所有这些都是完全没有意义的复杂性,它使程序变得更慢、更错误,而且毫无意义!

标签: c multithreading image-processing


【解决方案1】:

好吧,这不像是关于您的代码的一些观察结果那样的答案:

  • 您似乎并没有真正从程序中任何地方的多个线程访问一个特定的内存单元。因此,似乎不需要静音。

  • 或者,也许线程确实访问了相同的内存段。在这种情况下,您的程序似乎很有可能只用一个线程来完成所有计算,效率会高得多。您应该对这种情况进行基准测试并将其与线程版本进行比较。

  • 至少对我来说,这里没有明显的理由需要多线程。如果您在单个线程中进行这些浮点计算,它们很可能在操作系统甚至设法产生第二个线程之前完成。与线程创建开销时间相比,工作负载微不足道。

  • 您当前的多线程设计存在缺陷,所有工作都在受互斥体保护的代码中进行。互斥锁之外并没有什么实际的工作可以做,所以不管你创建1000个线程,一次只能执行1个,其他的会睡着等待轮到他们。

【讨论】:

  • 感谢您的回答!这里的互斥锁只是为了防止数据竞争(我的错)的绝望尝试,这最终是由错误的指针副本引起的(我并不完全习惯于使用 C 的所有这些指针,我在 2 个月前开始使用 C )。现在互斥体消失了,指针更正了,代码可以工作了!
  • @HappyRave 很高兴听到。您应该将错误原因和解决方案作为您自己问题的单独答案发布,而不是编辑您的问题,然后将其作为正确答案接受。这是为未来的读者分享信息的正确方式 - 只要其他人也有公平的机会回答它,在 SO 上回答你自己的问题是完全可以的。
  • 但是必须等待 8 小时才能回复我自己的帖子,因为我的声望不到 10 ...
【解决方案2】:

首先,非常感谢您的帮助!感谢您的回答,我设法修复了我的代码:-)

由于相当多的 cmets 指出我的互斥锁无用,我还认为它们更像是我的程序性能的瓶颈,而不是我的问题的解决方案。 我之所以添加它们,是因为我希望它们能神奇地解决我的问题(奇迹有时会在编程中发生)。现在他们走了(他们应该永远不会来了),而且代码更快了!

回到原来的问题!为了应用我的模糊滤镜,我需要我的图像的只读副本。为了获得这个副本,我使用了 memcpy,如下所示:

struct image* img2 = (struct image*)malloc(sizeof(struct image));
memcpy(img2,img,sizeof(struct image));

但正如@jop 指出的那样,即使我正在复制img,指针pixels 指向de 复制的img2 内部分配的内存仍然指向原始数组。 因此,不是复制img,而是复制img-&gt;pixels 就行了。我修改了我的代码:

struct pixel* pixels = (struct pixel*)malloc(sizeof(struct pixel)*img->width*img->height);
memcpy(pixels,img->pixels,sizeof(struct pixel)*img->width*img->height);

瞧,问题解决了!所以谢谢大家!

一些 cmets 还讨论了是否需要使用线程。好吧,在这种情况下,我别无选择,因为该项目的目标是编写一些并行化的图像过滤器。所以,是的,需要线程!

【讨论】:

    猜你喜欢
    • 2014-11-08
    • 1970-01-01
    • 2016-10-29
    • 2012-10-27
    • 1970-01-01
    • 2012-08-10
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多