并发:使用pthread_mutex原子化一个增量操作
Concurrency: Using pthread_mutex to atomize an increment operation
我目前正在阅读 操作系统:三篇简单的文章,我开始了解并发背后的逻辑。在 26 "chapter" 中,我们得到了这个线程示例和围绕原子性的问题:
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
static volatile int counter = 0;
// mythread()
// Simply adds 1 to counter repeatedly, in a loop
// No, this is not how you would add 10,000,000 to
// a counter, but it shows the problem nicely.
void *mythread(void *arg){
printf("%s: begin\n", (char *) arg);
int i;
for (i = 0; i < 1e7; i++) {
counter = counter + 1;
}
printf("%s: done\n", (char *) arg);
return NULL;
}
// main()
// Just launches two threads (pthread_create)
// and then waits for them (pthread_join)
int main(int argc, char *argv[]) {
pthread_t p1, p2;
printf("main: begin (counter = %d)\n", counter);
pthread_create(&p1, NULL, mythread, "A");
pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("main: done with both (counter = %d)\n", counter);
return 0;
}
它向我们展示了问题是,由于增量中的竞争条件,总和的值会发生变化,而且很少是预期的。
举例,编译后:
gcc -g -o main page6.c -Wall -pthread
和 运行 两次我得到:
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 10263001)
和:
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 10600399)
所以在阅读了有关互斥锁的内容后,我尝试对 mythread() 函数中的代码进行这个小改动。
void *mythread(void *arg){
pthread_mutex_t lock;
int rc = pthread_mutex_init(&lock,NULL);
assert(rc==0); //always check sucess
printf("%s: begin\n", (char *) arg);
int i;
for (i = 0; i < 1e7; i++) {
pthread_mutex_lock(&lock);
counter = counter + 1;
pthread_mutex_unlock(&lock);
}
printf("%s: done\n", (char *) arg);
return NULL;
}
并且在编译(以相同的方式)和 运行 之后,确实需要更多的时间(1-2 秒)。
但结果也好不到哪里去:
main: begin (counter = 0)
A: begin
B: begin
B: done
A: done
main: done with both (counter = 10019830)
和:
main: begin (counter = 0)
A: begin
B: begin
B: done
A: done
main: done with both (counter = 10008806)
那为什么这行不通呢?这不应该在代码中创建一个关键部分吗?我是否漏掉了一些明显的东西?
您不应该将互斥量作为 mythread
中的局部变量,因为每个线程都会获得自己的副本。使其成为全局并在 main 中对其进行初始化。
我目前正在阅读 操作系统:三篇简单的文章,我开始了解并发背后的逻辑。在 26 "chapter" 中,我们得到了这个线程示例和围绕原子性的问题:
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
static volatile int counter = 0;
// mythread()
// Simply adds 1 to counter repeatedly, in a loop
// No, this is not how you would add 10,000,000 to
// a counter, but it shows the problem nicely.
void *mythread(void *arg){
printf("%s: begin\n", (char *) arg);
int i;
for (i = 0; i < 1e7; i++) {
counter = counter + 1;
}
printf("%s: done\n", (char *) arg);
return NULL;
}
// main()
// Just launches two threads (pthread_create)
// and then waits for them (pthread_join)
int main(int argc, char *argv[]) {
pthread_t p1, p2;
printf("main: begin (counter = %d)\n", counter);
pthread_create(&p1, NULL, mythread, "A");
pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("main: done with both (counter = %d)\n", counter);
return 0;
}
它向我们展示了问题是,由于增量中的竞争条件,总和的值会发生变化,而且很少是预期的。
举例,编译后:
gcc -g -o main page6.c -Wall -pthread
和 运行 两次我得到:
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 10263001)
和:
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 10600399)
所以在阅读了有关互斥锁的内容后,我尝试对 mythread() 函数中的代码进行这个小改动。
void *mythread(void *arg){
pthread_mutex_t lock;
int rc = pthread_mutex_init(&lock,NULL);
assert(rc==0); //always check sucess
printf("%s: begin\n", (char *) arg);
int i;
for (i = 0; i < 1e7; i++) {
pthread_mutex_lock(&lock);
counter = counter + 1;
pthread_mutex_unlock(&lock);
}
printf("%s: done\n", (char *) arg);
return NULL;
}
并且在编译(以相同的方式)和 运行 之后,确实需要更多的时间(1-2 秒)。
但结果也好不到哪里去:
main: begin (counter = 0)
A: begin
B: begin
B: done
A: done
main: done with both (counter = 10019830)
和:
main: begin (counter = 0)
A: begin
B: begin
B: done
A: done
main: done with both (counter = 10008806)
那为什么这行不通呢?这不应该在代码中创建一个关键部分吗?我是否漏掉了一些明显的东西?
您不应该将互斥量作为 mythread
中的局部变量,因为每个线程都会获得自己的副本。使其成为全局并在 main 中对其进行初始化。