为什么我的程序使用线程抛出 14000000 而不是 10000000?

why my program throws 14000000 instead of 10000000 using threads?

我编写了一个简单的 C 程序,使每个线程将其索引乘以 1000000 并将其添加到总和中,我创建了 5 个线程,因此逻辑答案为 (0+1+2+3+4)*1000000是 10000000,但它抛出 14000000。有人能帮我理解这个吗?

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

typedef struct argument {
    int index;
    int sum;
} arg;

void *fonction(void *arg0) {
    ((arg *) arg0) -> sum += ((arg *) arg0) -> index * 1000000;
}
int main() {
    pthread_t thread[5];
    int order[5];
    arg a;
    for (int i = 0; i < 5; i++)
        order[i] = i;
    a.sum = 0;
    for (int i = 0; i < 5; i++) {
        a.index = order[i];
        pthread_create(&thread[i], NULL, fonction, &a);
    }
    for (int i = 0; i < 5; i++)
        pthread_join(thread[i], NULL);

    printf("%d\n", a.sum);

    return 0;
}

它是 140.. 因为行为未定义。结果将因不同的机器和其他环境因素而异。未定义的行为是由于所有线程访问同一个对象(参见 &a 给每个线程)引起的,该对象在创建第一个线程后被修改。

  • 当每个线程 运行 访问相同的 index(作为访问同一对象 (&a) 成员的一部分)。因此,线程将看到 [0,1,2,3,4] 的假设是不正确的:多个线程 可能 看到 index 的相同值(例如 [0 ,2,4,4,4]1) 当他们 运行。这取决于循环创建线程的调度,因为它还会修改共享对象。

  • 当每个线程更新 sum 时,它必须读取和写入相同的共享内存。这本质上容易出现竞争条件和不可靠的结果。例如,它可能缺乏内存可见性(线程 X 看不到线程 Y 更新的值)或者它可能是读写之间的线程调度冲突(线程 X 读取,线程 Y 读取,线程 X 写入,线程Y写)等..

如果为每个线程创建一个新的 arg 对象,那么这两个问题都可以避免。虽然可以通过适当的锁定解决总和问题,但只能通过不共享作为线程输入给定的对象来解决索引问题。

// create 5 arg objects, one for each thread
arg a[5];

for (..) {
    a[i].index = i;
    // give DIFFERENT object to each thread
    pthread_create(.., &a[i]);
}

// after all threads complete
int sum = 0;
for (..) {
    sum += a[i].result;
}

1 即使假设当前执行中没有竞争条件 wrt。 sum 的用法,将 index 值视为 [0,2,4,4,4] 的不同线程的序列,其总和为 14,可能如下所示:

  1. a.index <- 0 ;创建线程 A
  2. 线程 A 读取 a.index (0)
  3. a.index <- 1 ;创建线程 B
  4. a.index <- 2 ;创建线程 C
  5. 线程 B 读取 a.index (2)
  6. a.index <- 3 ;创建线程 D
  7. a.index <- 4 ;创建线程 E
  8. 线程 D 读取 a.index (4)
  9. 线程 C 读取 a.index (4)
  10. 线程 E 读取 a.index (4)