简体   繁体   中英

Data races when using mutexes

I'm working on a small project in C involving threads and mutexes. The program i'm working on applies filters on bmp mages. The goal of the project is to implement a program capable of handling this command line:

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

Where -f are the filters I want to apply ("red","blue","green","grayscale" and "blur"), and -t are the numbers of threads allocated per filter.

So far everything's fine except blur, where i'm stuck on data races (or, I think so). The blur filter work like this:

/* 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
*/

The thing is, when I run my program on a "chessboard" image with this blur filter:

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

I'm expecting to get this image , but i'm getting this ("broken" lines vary randomly)

I'm using mutexes to lock and unlock the critical section, but as you can see data races still occurs. Just two words over my filter, I give each thread a line at a time, starting from the bottom and going up. My code for filter_blur is:

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;
}

EDIT 1 :

As @job correctly guessed, the problem was caused by memcpy of my structure (the structure was copied, but the pointers inside the structure were still pointing to the original structure elements). I have also removed the mutexes (they were just here because I tought they could solve my problem, sorry, my bad) Now my project is working like a charm (even if we can still discuss the processing speed, and the need to use threads). As I said, it's a project, an University project for my C class. And the goal is to parallelize our filters. So threads are needed.

Thank You!

Alright, this isn't as much an answer as a number of observations regarding your code:

  • You don't seem to actually access one particular memory cell from several threads anywhere in your program. So it would seem that the mutices aren't needed.

  • Or alternatively, perhaps the threads do access the same memory segments. In that case, it seems very likely that your program would be far more efficient with just one single thread doing all the calculations. You should benchmark this case and compare it with the threaded version.

  • At least to me, there is no apparent reason why multi-threading would be needed here. If you make those float calculations in a single thread, they are likely done before the OS has even managed to spawn a second thread. The workload is insignificant compared to the thread creation overhead time.

  • Your current multi-thread design is flawed, with all the work going on inside the mutex protected code. There is no actual work that can be done outside the mutex lock, so no matter if you create 1000 threads, only 1 can execute at a time and the others will be asleep waiting for their turn.

First of all, thank you very much for your help! I managed to fix my code thanks to your answers :-)

As a fair number of comments pointed the uselessness of my mutexes, I also thought they were more like a bottleneck for my program performance than a solution to my issue. I only added them because I hoped that they will magically fix my problem (miracles sometimes happen in programming). Now they are gone (they should have never come), and the code is faster!

Back to original problem! For the application of my blur filter, I needed a read only copy of my image. To obtain this copy, I used memcpy, like this:

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

But as @jop pointed this out, even if I was copying img , the pointer pixels to an allocated memory inside de copied img2 was still pointing to the original array. So, instead of copying img , copying img->pixels did the trick. I modified my code whith this:

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

And voilà, problem fixed! So thank you all!

Some comments also discussed the need of using, or not threads. Well, in this case, I don't have any choice, because the goal of the project is to write some parallelized image filters. So yeah, threads needed!.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM