了解并行线程执行

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_waitsem_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