了解并行线程执行
Understanding parallel thread execution
编写简单的 C 代码,试图控制来自两个不同线程的输出:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
sem_t sem;
void* thread_func(void* aArgs)
{
printf("Entering thread %p with %d\n", (void*)pthread_self(), (int)aArgs);
int i = 0;
for(;i < 10; i++)
{
sem_wait(&sem);
if ((i % 2) == (int)aArgs)
printf("val is %d in thread %p \n", i, (void*)pthread_self());
sem_post(&sem);
}
}
int main()
{
pthread_t thread_1, thread_2;
sem_init(&sem, 0, 1);
pthread_create(&thread_1, NULL, (void*)thread_func, (void*)0);
pthread_create(&thread_2, NULL, (void*)thread_func, (void*)1);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
sem_destroy(&sem);
return 0;
}
我想要实现的是混合奇数和偶数的序列。但是我从一个线程接收到所有数字,然后从第二个线程接收所有其他数字,就像这样(即使我增加了循环计数器的大小):
Entering thread 0xb75f2b40 with 0
val is 0 in thread 0xb75f2b40
val is 2 in thread 0xb75f2b40
val is 4 in thread 0xb75f2b40
val is 6 in thread 0xb75f2b40
val is 8 in thread 0xb75f2b40
Entering thread 0xb6df1b40 with 1
val is 1 in thread 0xb6df1b40
val is 3 in thread 0xb6df1b40
val is 5 in thread 0xb6df1b40
val is 7 in thread 0xb6df1b40
val is 9 in thread 0xb6df1b40
问题是为什么两个独立的线程表现得像两个顺序任务?为什么第二个线程直到第一个线程还没有完成所有的事情才取得执行控制权?
我尝试将 pthread_yield() 添加到 for 循环的末尾,但情况并没有显着改变:有时我得到预期的输出,有时 - 如上所述。
更新。我怎样才能实现确定性的一对一线程执行?是否有任何同步原语?
您在循环的 相同迭代 中继续调用 sem_wait
和 sem_post
,因此线程在其持续时间内保持对信号量的控制时间片 - 一旦 sem_post
被调用,sem_wait
会在接下来的迭代中立即再次调用(在同一线程中)。
这是使用条件变量解决您的问题的方法:
pthread_mutex_t mut;
pthread_cond_t print_cond;
int print_thread; //equals 0 or 1
这些是用于同步两个线程之间输出的全局变量。 print_thread
当我们希望第一个线程打印时等于 0,当我们希望第二个线程打印时等于 1。
在里面 thread_func
:
for(;i < 10; i++)
{
pthread_mutex_lock(&mut);
if ((i % 2) == (int)aArgs){
while (print_thread != (int)aArgs){
pthread_cond_wait(&print_cond, &mut);
}
printf("val is %d in thread %p \n", i, (void*)pthread_self());
print_thread = 1 - (int)aArgs;
pthread_cond_signal(&print_cond);
pthread_mutex_unlock(&mut);
} else {
pthread_mutex_unlock(&mut);
}
}
使用此代码,您应该得到类似于以下内容的输出:
Entering thread 0xb6fbcb70 with 1
Entering thread 0xb77bdb70 with 0
val is 0 in thread 0xb77bdb70
val is 1 in thread 0xb6fbcb70
val is 2 in thread 0xb77bdb70
val is 3 in thread 0xb6fbcb70
val is 4 in thread 0xb77bdb70
val is 5 in thread 0xb6fbcb70
val is 6 in thread 0xb77bdb70
val is 7 in thread 0xb6fbcb70
val is 8 in thread 0xb77bdb70
val is 9 in thread 0xb6fbcb70
请注意,此解决方案可以很好地扩展到不止两个线程的打印:唯一需要的更改是适当地更新 print_thread
。
pthread_yield()
是一个非标准调用,使用 sched.h 中的 sched_yield()
代替。
此外,我会在 0 处初始化信号量,并在创建两个线程后调用 sem_post
。
所以线程的代码看起来像
for(;i < 10; i++)
{
sem_wait(&sem);
if ((i % 2) == (int)aArgs)
printf("val is %d in thread %p \n", i, (void*)pthread_self());
sem_post(&sem);
sched_yield();
}
主要是:
sem_init(&sem, 0, 0);
pthread_create(&thread_1, NULL, (void*)thread_func, (void*)0);
pthread_create(&thread_2, NULL, (void*)thread_func, (void*)1);
sem_post(&sem);
得到的是:
Entering thread 0x7f74c7697700 with 0
val is 0 in thread 0x7f74c7697700
Entering thread 0x7f74c6e96700 with 1
val is 2 in thread 0x7f74c7697700
val is 4 in thread 0x7f74c7697700
val is 1 in thread 0x7f74c6e96700
val is 3 in thread 0x7f74c6e96700
val is 5 in thread 0x7f74c6e96700
val is 7 in thread 0x7f74c6e96700
val is 6 in thread 0x7f74c7697700
val is 9 in thread 0x7f74c6e96700
val is 8 in thread 0x7f74c7697700
如果你想得到想要的输出,你应该使用两个信号量而不是一个。在每个循环迭代完成后,每个线程都应该等待自己的信号量和 post 另一个线程的信号量。主线程可以创建一个值为 1 的信号量和另一个值为零的信号量以正确开始。这将强制两个线程以交替顺序运行。
由于程序是当前编写的,执行 sem_post
后跟 sem_wait
可能会导致同一个线程立即获取信号量(在单个 cpu 系统上).我很惊讶 pthread_yield
没有帮助,但是无论如何使用两个信号量将保证正确的顺序。
只想演示 by JS1 的代码,适用于任意数量的线程:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM 3
static sem_t sem[NUM];
static void *thread_func(void *args)
{
int i;
for (i = 0; i < 10; ++i) {
int cur = (long)args; /* current thread number */
int next = (cur + 1) % NUM; /* next thread number*/
if ((i % NUM) != cur)
continue;
sem_wait(&sem[cur]); /* lock this thread's semaphore */
printf("val is %d, thread num = %ld\n", i, (long)args);
sem_post(&sem[next]); /* unlock next thread's semaphore */
}
return NULL;
}
int main(void)
{
size_t i;
pthread_t t[NUM];
for (i = 0; i < NUM; ++i)
sem_init(&sem[i], 0, 0); /* locked */
for (i = 0; i < NUM; ++i)
pthread_create(&t[i], NULL, thread_func, (void *)i);
sem_post(&sem[0]);
for (i = 0; i < NUM; ++i)
pthread_join(t[i], NULL);
for (i = 0; i < NUM; ++i)
sem_destroy(&sem[i]);
return 0;
}
输出:
val is 0, thread num = 0
val is 1, thread num = 1
val is 2, thread num = 2
val is 3, thread num = 0
val is 4, thread num = 1
val is 5, thread num = 2
val is 6, thread num = 0
val is 7, thread num = 1
val is 8, thread num = 2
val is 9, thread num = 0
编写简单的 C 代码,试图控制来自两个不同线程的输出:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
sem_t sem;
void* thread_func(void* aArgs)
{
printf("Entering thread %p with %d\n", (void*)pthread_self(), (int)aArgs);
int i = 0;
for(;i < 10; i++)
{
sem_wait(&sem);
if ((i % 2) == (int)aArgs)
printf("val is %d in thread %p \n", i, (void*)pthread_self());
sem_post(&sem);
}
}
int main()
{
pthread_t thread_1, thread_2;
sem_init(&sem, 0, 1);
pthread_create(&thread_1, NULL, (void*)thread_func, (void*)0);
pthread_create(&thread_2, NULL, (void*)thread_func, (void*)1);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
sem_destroy(&sem);
return 0;
}
我想要实现的是混合奇数和偶数的序列。但是我从一个线程接收到所有数字,然后从第二个线程接收所有其他数字,就像这样(即使我增加了循环计数器的大小):
Entering thread 0xb75f2b40 with 0
val is 0 in thread 0xb75f2b40
val is 2 in thread 0xb75f2b40
val is 4 in thread 0xb75f2b40
val is 6 in thread 0xb75f2b40
val is 8 in thread 0xb75f2b40
Entering thread 0xb6df1b40 with 1
val is 1 in thread 0xb6df1b40
val is 3 in thread 0xb6df1b40
val is 5 in thread 0xb6df1b40
val is 7 in thread 0xb6df1b40
val is 9 in thread 0xb6df1b40
问题是为什么两个独立的线程表现得像两个顺序任务?为什么第二个线程直到第一个线程还没有完成所有的事情才取得执行控制权?
我尝试将 pthread_yield() 添加到 for 循环的末尾,但情况并没有显着改变:有时我得到预期的输出,有时 - 如上所述。
更新。我怎样才能实现确定性的一对一线程执行?是否有任何同步原语?
您在循环的 相同迭代 中继续调用 sem_wait
和 sem_post
,因此线程在其持续时间内保持对信号量的控制时间片 - 一旦 sem_post
被调用,sem_wait
会在接下来的迭代中立即再次调用(在同一线程中)。
这是使用条件变量解决您的问题的方法:
pthread_mutex_t mut;
pthread_cond_t print_cond;
int print_thread; //equals 0 or 1
这些是用于同步两个线程之间输出的全局变量。 print_thread
当我们希望第一个线程打印时等于 0,当我们希望第二个线程打印时等于 1。
在里面 thread_func
:
for(;i < 10; i++)
{
pthread_mutex_lock(&mut);
if ((i % 2) == (int)aArgs){
while (print_thread != (int)aArgs){
pthread_cond_wait(&print_cond, &mut);
}
printf("val is %d in thread %p \n", i, (void*)pthread_self());
print_thread = 1 - (int)aArgs;
pthread_cond_signal(&print_cond);
pthread_mutex_unlock(&mut);
} else {
pthread_mutex_unlock(&mut);
}
}
使用此代码,您应该得到类似于以下内容的输出:
Entering thread 0xb6fbcb70 with 1
Entering thread 0xb77bdb70 with 0
val is 0 in thread 0xb77bdb70
val is 1 in thread 0xb6fbcb70
val is 2 in thread 0xb77bdb70
val is 3 in thread 0xb6fbcb70
val is 4 in thread 0xb77bdb70
val is 5 in thread 0xb6fbcb70
val is 6 in thread 0xb77bdb70
val is 7 in thread 0xb6fbcb70
val is 8 in thread 0xb77bdb70
val is 9 in thread 0xb6fbcb70
请注意,此解决方案可以很好地扩展到不止两个线程的打印:唯一需要的更改是适当地更新 print_thread
。
pthread_yield()
是一个非标准调用,使用 sched.h 中的 sched_yield()
代替。
此外,我会在 0 处初始化信号量,并在创建两个线程后调用 sem_post
。
所以线程的代码看起来像
for(;i < 10; i++)
{
sem_wait(&sem);
if ((i % 2) == (int)aArgs)
printf("val is %d in thread %p \n", i, (void*)pthread_self());
sem_post(&sem);
sched_yield();
}
主要是:
sem_init(&sem, 0, 0);
pthread_create(&thread_1, NULL, (void*)thread_func, (void*)0);
pthread_create(&thread_2, NULL, (void*)thread_func, (void*)1);
sem_post(&sem);
得到的是:
Entering thread 0x7f74c7697700 with 0
val is 0 in thread 0x7f74c7697700
Entering thread 0x7f74c6e96700 with 1
val is 2 in thread 0x7f74c7697700
val is 4 in thread 0x7f74c7697700
val is 1 in thread 0x7f74c6e96700
val is 3 in thread 0x7f74c6e96700
val is 5 in thread 0x7f74c6e96700
val is 7 in thread 0x7f74c6e96700
val is 6 in thread 0x7f74c7697700
val is 9 in thread 0x7f74c6e96700
val is 8 in thread 0x7f74c7697700
如果你想得到想要的输出,你应该使用两个信号量而不是一个。在每个循环迭代完成后,每个线程都应该等待自己的信号量和 post 另一个线程的信号量。主线程可以创建一个值为 1 的信号量和另一个值为零的信号量以正确开始。这将强制两个线程以交替顺序运行。
由于程序是当前编写的,执行 sem_post
后跟 sem_wait
可能会导致同一个线程立即获取信号量(在单个 cpu 系统上).我很惊讶 pthread_yield
没有帮助,但是无论如何使用两个信号量将保证正确的顺序。
只想演示
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM 3
static sem_t sem[NUM];
static void *thread_func(void *args)
{
int i;
for (i = 0; i < 10; ++i) {
int cur = (long)args; /* current thread number */
int next = (cur + 1) % NUM; /* next thread number*/
if ((i % NUM) != cur)
continue;
sem_wait(&sem[cur]); /* lock this thread's semaphore */
printf("val is %d, thread num = %ld\n", i, (long)args);
sem_post(&sem[next]); /* unlock next thread's semaphore */
}
return NULL;
}
int main(void)
{
size_t i;
pthread_t t[NUM];
for (i = 0; i < NUM; ++i)
sem_init(&sem[i], 0, 0); /* locked */
for (i = 0; i < NUM; ++i)
pthread_create(&t[i], NULL, thread_func, (void *)i);
sem_post(&sem[0]);
for (i = 0; i < NUM; ++i)
pthread_join(t[i], NULL);
for (i = 0; i < NUM; ++i)
sem_destroy(&sem[i]);
return 0;
}
输出:
val is 0, thread num = 0
val is 1, thread num = 1
val is 2, thread num = 2
val is 3, thread num = 0
val is 4, thread num = 1
val is 5, thread num = 2
val is 6, thread num = 0
val is 7, thread num = 1
val is 8, thread num = 2
val is 9, thread num = 0