我试图通过使用自旋锁解决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
通常,临界区内的变量不需要是易失性的,因为它不会异步更改。有人可以帮我理解编译器优化导致了这个问题吗?
答案 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