Concurrency
多线程编程
多线程并发执行时,由于处理器调度的不确定性,各线程指令会以任何可能的方式交叠执行。此时,当多个线程并发访问共享变量或资源时,由于缺乏操作的原子性、顺序性和一致性等问题,就可能产生预料之外的后果。
无法保证原子性
当两个线程一起调用 pay(100) 时,会对共享变量 balance 产生预料之外的影响 (代码的交替执行)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
unsigned long balance = 100;
void *pay(void *arg) {
int amount = *((int*) arg);
if (balance >= amount) {
usleep(1); // some unexpected delays
balance -= amount;
}
return NULL;
}
int main(void) {
pthread_t card, phone;
int num = 100;
pthread_create(&card, NULL, buy_meals, (void*)&num);
pthread_create(&phone, NULL, buy_meals, (void*)&num);
pthread_join(card, NULL);
pthread_join(phone, NULL);
printf("Balance: %lu\n", balance);
return 0;
}
当两个线程一起对共享变量 count 进行 count++ 操作时,最终输出的 count 不会如预期一样等于 2000000 (指令的交替执行)。 此时,即使把 count++ 替换为一条指令 asm volatile("incq %0" : "+m" (count)),也无法得到正确的预期结果 (在多核下多个线程的指令根本就是并行执行的)。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM 1000000
int count = 0;
void* func(void *arg) {
for (int i = 0; i < NUM; i++) {
count++;
}
return NULL;
}
int main(void) {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, func, NULL);
pthread_create(&thread2, NULL, func, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Count: %d\n", count);
return 0;
}
无法保证顺序性
使用不同的编译优化选项会对代码行为产生影响。例如,使用 -O1 编译上述 count++ 代码会观察到 1000000;而使用 -O2 编译上述代码则会观察到 2000000 (此时程序行为一定是正确的吗?)。
此外,对于下述代码 (尝试实现一种 T1 等待 T2 的行为),在不进行编译优化和使用 -O2 进行优化时,程序也会表现出不同的行为。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int flag = 0;
int x = 0;
void *T1(void* arg) {
while (flag == 0)
;
printf("%d\n", x);
return NULL;
}
void *T2(void* arg) {
x = 10;
flag = 1;
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, T1, NULL);
pthread_create(&thread2, NULL, T2, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}