volatile整数如何解决此线程同步问题?

时间:2017-06-16 16:46:17

标签: c multithreading

我试图通过使用自旋锁解决bounder缓冲问题。当使用-O选项编译程序时,条件变量“lock”需要定义为volatile,因为没有它,读者将永远旋转“while(lock == 0)。但是,我发现甚至”count“也需要定义为volatile。请参阅下面的代码。

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

int count = 0;
volatile int lock = 0;

#define NUM_COUNT       5

static void *
writer(void *arg) {
    int i = 0;

    for(i=0; i < NUM_COUNT; i++) {
        while(lock == 1) {
        }

        printf("Writer count %d ", ++count);
        lock = 1;
    }

    return NULL;
}

static void *
reader(void *arg) {
    static int k = 0;

    while(count < NUM_COUNT) {
        while(lock == 0) {
        }

        printf("reader count %d \n", count);
        if (k > 0 && count!=++k) {
           assert(0);
           printf("reader count %d %d\n", count, k);
        }
        k = count;
        lock = 0;
    }

    return NULL;
}

int
main(void)
{
    pthread_t writer_pthread_id;
    pthread_t reader_pthread_id1;
    void *res;

    pthread_create(&reader_pthread_id1, NULL, reader, NULL);
    pthread_create(&writer_pthread_id, NULL, writer, NULL);


    pthread_join(writer_pthread_id, &res);
    printf("Joined with thread id %lu; return value was %p\n",
           writer_pthread_id, (char *)res);

    pthread_join(reader_pthread_id1, &res);
    printf("Joined with thread id %lu; return value was %p\n",
           reader_pthread_id1, (char *)res);


    printf("count = %d\n", count);

    return 0;
}

根据输出,读者似乎没有读取当前值。

bash-3.2$ gcc -g -Wall -pthread -O    thread_synchro2_bounded_buffer_spin_lock.c bash-3.2$ ./a.out 
Writer count 1 reader count 0 
Writer count 2 reader count 1 
Writer count 3 reader count 2 
Writer count 4 reader count 3 
Writer count 5 reader count 4 
Joined with thread id 47779619322176; return value was (nil)    
Joined with thread id 47779608832320; return value was (nil) 
count =    5

但是,如果我将“count”更改为“volatile int count”,那么问题就会得到解决。

bash-3.2$ gcc -g -Wall -pthread -O thread_synchro2_bounded_buffer_spin_lock.c
bash-3.2$ ./a.out
Writer count 1 reader count 1
Writer count 2 reader count 2
Writer count 3 reader count 3
Writer count 4 reader count 4
Writer count 5 reader count 5
Joined with thread id 47040774805824; return value was (nil)
Joined with thread id 47040764315968; return value was (nil)
count = 5

通常,临界区内的变量不需要是易失性的,因为它不会异步更改。有人可以帮我理解编译器优化导致了这个问题吗?

1 个答案:

答案 0 :(得分:1)

volatile int count和常规int count之间的差异的可能原因是读者线程中的编译器优化。

由于它需要在阅读器线程中评估while (count < NUM_COUNT),因此它已在寄存器中有count,后来又无需从内存中读取 再次做printf("reader count %d \n", count);。当count易变时,它必须再次读取它。在这些语句之间,作者线程更新count

但多线程很棘手且容易出错。最好使用精心设计和测试的惯用方法(如原子库),或者完全避免它,并在计算的其他部分使用并行性。

---更新---

这是为两个版本的reader线程生成的程序集的差异。它证实了上述假设。对于那些解析它的人来说,相关的变化是在.LVL10之上添加的两行,其余的更改主要是重新计算:

--- reader.s.nonvolatile    2017-06-16 20:58:26.680644709 +0300
+++ reader.s.volatile   2017-06-16 20:58:29.143644664 +0300
@@ -6,8 +6,8 @@
    .cfi_startproc
 .LVL8:
    .loc 1 29 0
-   movl    count(%rip), %edx
-   cmpl    $4, %edx
+   movl    count(%rip), %eax
+   cmpl    $4, %eax
    jg  .L14
    .loc 1 26 0
    subq    $8, %rsp
@@ -18,6 +18,8 @@
    movl    lock(%rip), %eax
    testl   %eax, %eax
    je  .L10
+   .loc 1 33 0
+   movl    count(%rip), %edx        <--- Reads count again!
 .LVL10:
 .LBB14:
 .LBB15:
@@ -36,7 +38,8 @@
    .loc 1 34 0 is_stmt 0 discriminator 1
    addl    $1, %eax
    movl    %eax, k.3008(%rip)
-   cmpl    count(%rip), %eax
+   movl    count(%rip), %edx
+   cmpl    %edx, %eax
    je  .L11
    .loc 1 35 0 is_stmt 1
    movl    $__PRETTY_FUNCTION__.3012, %ecx
@@ -52,7 +55,7 @@
    .loc 1 39 0
    movl    $0, lock(%rip)
    .loc 1 29 0
-   movl    %eax, %edx
+   movl    count(%rip), %eax
    cmpl    $4, %eax
    jle .L10
    .loc 1 43 0