pthread_join 导致奇怪的执行顺序
pthread_join causes a weird execution sequence
这是我的测试 C 程序如下:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
__thread int var = 0;
void* worker(void* arg);
int main()
{
pthread_t pid1, pid2;
pthread_create(&pid1, NULL, worker, (void*)0);
pthread_create(&pid2, NULL, worker, (void*)1);
printf("-----------1----------\n");
pthread_join(pid1, NULL);
sleep(1);
printf("-----------2----------\n");
pthread_join(pid2, NULL);
return 0;
}
void* worker(void* arg)
{
int idx = (int)arg;
int i;
for (i = 0; i < 10; ++i) {
printf("thread: %d ++var = %d\n",
idx,
++var);
}
}
然后我编译如下:
$ gcc -g -Wall -pthread 1.c -lpthread -o test
1.c: In function ‘worker’:
1.c:27:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
27 | int idx = (int)arg;
| ^
1.c:35:1: warning: control reaches end of non-void function [-Wreturn-type]
35 | }
| ^
运行结果如下:1秒后最后一行出来。但我不明白为什么 "thread: 1"
出现在 "thread: 0"
之前?
$ ./test
-----------1----------
thread: 1 ++var = 1
thread: 1 ++var = 2
thread: 1 ++var = 3
thread: 1 ++var = 4
thread: 1 ++var = 5
thread: 1 ++var = 6
thread: 1 ++var = 7
thread: 1 ++var = 8
thread: 1 ++var = 9
thread: 1 ++var = 10
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 0 ++var = 5
thread: 0 ++var = 6
thread: 0 ++var = 7
thread: 0 ++var = 8
thread: 0 ++var = 9
thread: 0 ++var = 10
-----------2----------
why "thread: 1" comes before "thread: 0"?
只是线程1先得到了CPU时间,而且速度很快,可以全部打印出来
最有可能发生的事情是,main()
有 CPU 时间,然后 main()
运行 pthread_join
,在这种情况下它产生处理器时间并且调度程序启动。然后调度程序决定给线程 1 CPU 时间——可能是最后一个,任意选择。线程足够快,可以在调度程序能够重新安排 CPU 时间之前将其全部打印出来。
线程彼此无序 - 你不能期望任何顺序,除了 printf
输出不应该混合,即 printf
本身是线程安全的。一个线程在另一个线程之前打印与任何其他结果一样没有顺序。
warning: cast from pointer to integer of different size
执行 (uintptr_t)arg;
以消除警告。
warning: control reaches end of non-void function
这是一个非常严重的警告,会导致未定义的行为。特别是,最近我探索了这个确切的问题如何导致 与您的代码非常相似。在函数末尾添加return NULL;
。
想象一下多核系统中的情况,其中每个线程执行都分配给不同的处理器,它们将在某种程度上相互独立地执行代码,如果我们想要一个更快的程序,如果一个必须等待另一个线程结束才能开始,这样会使两个线程的使用变得无用。
pthread_join
s 保证的是,在两个线程都结束各自的工作之前,程序不会继续。
如果你想控制执行流程,那么你不应该有一个以上的线程,或者你必须自己同步执行,在这种情况下你可以使用互斥锁,它看起来像:
void* worker(void* arg);
typedef struct { // shared data
int idx;
int var;
pthread_mutex_t* mutex_ptr;
} Data;
int main()
{
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
Data data = {.idx = 0, .var = 0, .mutex_ptr = &mutex};
pthread_t pid1, pid2;
pthread_create(&pid1, NULL, worker, &data);
pthread_create(&pid2, NULL, worker, &data);
pthread_join(pid1, NULL);
pthread_join(pid2, NULL);
pthread_mutex_destroy(&mutex);
}
void* worker(void* arg)
{
Data* data = (Data*)arg;
int i;
pthread_mutex_lock(data->mutex_ptr);
printf("\n----------%d-----------\n", data->idx);
for (i = 0; i < 10; ++i) {
printf("thread: %d ++var = %d\n",
data->idx,
++data->var);
}
data->idx++;
data->var = 0;
pthread_mutex_unlock(data->mutex_ptr);
return NULL; // return type of worker is void* so it must return a pointer
}
在此 live sample 中,您可以看到使用与不使用互斥锁时的区别。我必须添加一个更大的循环才能真正看到它。
带互斥量的预期输出:
----------0-----------
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 0 ++var = 5
thread: 0 ++var = 6
thread: 0 ++var = 7
thread: 0 ++var = 8
thread: 0 ++var = 9
thread: 0 ++var = 10
----------1-----------
thread: 1 ++var = 1
thread: 1 ++var = 2
thread: 1 ++var = 3
thread: 1 ++var = 4
thread: 1 ++var = 5
thread: 1 ++var = 6
thread: 1 ++var = 7
thread: 1 ++var = 8
thread: 1 ++var = 9
thread: 1 ++var = 10
没有互斥锁任何命令都是有效的,例如:
----------0-----------
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 1 ++var = 1
thread: 0 ++var = 5
----------1-----------
thread: 0 ++var = 6
thread: 1 ++var = 2
thread: 0 ++var = 7
thread: 1 ++var = 3
thread: 0 ++var = 8
thread: 1 ++var = 4
...
...
...
请注意,由于我们引入的同步,同步示例在速度方面几乎没有利用线程,程序的行为方式类似于我们使用单线程,但因为我们同步我们可以控制对数据的访问,请注意互斥量是共享的,还要注意这是一个示例,但请记住关键部分,即锁内的内容,应该只是必要的数据,尽可能避免循环。
这是我的测试 C 程序如下:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
__thread int var = 0;
void* worker(void* arg);
int main()
{
pthread_t pid1, pid2;
pthread_create(&pid1, NULL, worker, (void*)0);
pthread_create(&pid2, NULL, worker, (void*)1);
printf("-----------1----------\n");
pthread_join(pid1, NULL);
sleep(1);
printf("-----------2----------\n");
pthread_join(pid2, NULL);
return 0;
}
void* worker(void* arg)
{
int idx = (int)arg;
int i;
for (i = 0; i < 10; ++i) {
printf("thread: %d ++var = %d\n",
idx,
++var);
}
}
然后我编译如下:
$ gcc -g -Wall -pthread 1.c -lpthread -o test
1.c: In function ‘worker’:
1.c:27:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
27 | int idx = (int)arg;
| ^
1.c:35:1: warning: control reaches end of non-void function [-Wreturn-type]
35 | }
| ^
运行结果如下:1秒后最后一行出来。但我不明白为什么 "thread: 1"
出现在 "thread: 0"
之前?
$ ./test
-----------1----------
thread: 1 ++var = 1
thread: 1 ++var = 2
thread: 1 ++var = 3
thread: 1 ++var = 4
thread: 1 ++var = 5
thread: 1 ++var = 6
thread: 1 ++var = 7
thread: 1 ++var = 8
thread: 1 ++var = 9
thread: 1 ++var = 10
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 0 ++var = 5
thread: 0 ++var = 6
thread: 0 ++var = 7
thread: 0 ++var = 8
thread: 0 ++var = 9
thread: 0 ++var = 10
-----------2----------
why "thread: 1" comes before "thread: 0"?
只是线程1先得到了CPU时间,而且速度很快,可以全部打印出来
最有可能发生的事情是,main()
有 CPU 时间,然后 main()
运行 pthread_join
,在这种情况下它产生处理器时间并且调度程序启动。然后调度程序决定给线程 1 CPU 时间——可能是最后一个,任意选择。线程足够快,可以在调度程序能够重新安排 CPU 时间之前将其全部打印出来。
线程彼此无序 - 你不能期望任何顺序,除了 printf
输出不应该混合,即 printf
本身是线程安全的。一个线程在另一个线程之前打印与任何其他结果一样没有顺序。
warning: cast from pointer to integer of different size
执行 (uintptr_t)arg;
以消除警告。
warning: control reaches end of non-void function
这是一个非常严重的警告,会导致未定义的行为。特别是,最近我探索了这个确切的问题如何导致 return NULL;
。
想象一下多核系统中的情况,其中每个线程执行都分配给不同的处理器,它们将在某种程度上相互独立地执行代码,如果我们想要一个更快的程序,如果一个必须等待另一个线程结束才能开始,这样会使两个线程的使用变得无用。
pthread_join
s 保证的是,在两个线程都结束各自的工作之前,程序不会继续。
如果你想控制执行流程,那么你不应该有一个以上的线程,或者你必须自己同步执行,在这种情况下你可以使用互斥锁,它看起来像:
void* worker(void* arg);
typedef struct { // shared data
int idx;
int var;
pthread_mutex_t* mutex_ptr;
} Data;
int main()
{
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
Data data = {.idx = 0, .var = 0, .mutex_ptr = &mutex};
pthread_t pid1, pid2;
pthread_create(&pid1, NULL, worker, &data);
pthread_create(&pid2, NULL, worker, &data);
pthread_join(pid1, NULL);
pthread_join(pid2, NULL);
pthread_mutex_destroy(&mutex);
}
void* worker(void* arg)
{
Data* data = (Data*)arg;
int i;
pthread_mutex_lock(data->mutex_ptr);
printf("\n----------%d-----------\n", data->idx);
for (i = 0; i < 10; ++i) {
printf("thread: %d ++var = %d\n",
data->idx,
++data->var);
}
data->idx++;
data->var = 0;
pthread_mutex_unlock(data->mutex_ptr);
return NULL; // return type of worker is void* so it must return a pointer
}
在此 live sample 中,您可以看到使用与不使用互斥锁时的区别。我必须添加一个更大的循环才能真正看到它。
带互斥量的预期输出:
----------0-----------
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 0 ++var = 5
thread: 0 ++var = 6
thread: 0 ++var = 7
thread: 0 ++var = 8
thread: 0 ++var = 9
thread: 0 ++var = 10
----------1-----------
thread: 1 ++var = 1
thread: 1 ++var = 2
thread: 1 ++var = 3
thread: 1 ++var = 4
thread: 1 ++var = 5
thread: 1 ++var = 6
thread: 1 ++var = 7
thread: 1 ++var = 8
thread: 1 ++var = 9
thread: 1 ++var = 10
没有互斥锁任何命令都是有效的,例如:
----------0-----------
thread: 0 ++var = 1
thread: 0 ++var = 2
thread: 0 ++var = 3
thread: 0 ++var = 4
thread: 1 ++var = 1
thread: 0 ++var = 5
----------1-----------
thread: 0 ++var = 6
thread: 1 ++var = 2
thread: 0 ++var = 7
thread: 1 ++var = 3
thread: 0 ++var = 8
thread: 1 ++var = 4
...
...
...
请注意,由于我们引入的同步,同步示例在速度方面几乎没有利用线程,程序的行为方式类似于我们使用单线程,但因为我们同步我们可以控制对数据的访问,请注意互斥量是共享的,还要注意这是一个示例,但请记住关键部分,即锁内的内容,应该只是必要的数据,尽可能避免循环。