为什么我的程序使用线程抛出 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,可能如下所示:
- a.index <- 0 ;创建线程 A
- 线程 A 读取 a.index (0)
- a.index <- 1 ;创建线程 B
- a.index <- 2 ;创建线程 C
- 线程 B 读取 a.index (2)
- a.index <- 3 ;创建线程 D
- a.index <- 4 ;创建线程 E
- 线程 D 读取 a.index (4)
- 线程 C 读取 a.index (4)
- 线程 E 读取 a.index (4)
我编写了一个简单的 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,可能如下所示:
- a.index <- 0 ;创建线程 A
- 线程 A 读取 a.index (0)
- a.index <- 1 ;创建线程 B
- a.index <- 2 ;创建线程 C
- 线程 B 读取 a.index (2)
- a.index <- 3 ;创建线程 D
- a.index <- 4 ;创建线程 E
- 线程 D 读取 a.index (4)
- 线程 C 读取 a.index (4)
- 线程 E 读取 a.index (4)