【问题标题】:Control the order of the threads ending控制线程结束的顺序
【发布时间】:2016-05-09 01:37:50
【问题描述】:

我有 4 个线程 (Thread_A - Thread_D)。我希望它们以 A、B、C、D 的顺序结束。必须用信号量来解决。

我的代码有什么问题?大多数情况下都可以,但有时顺序错误。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>

void* thread_A (void *arg);
void* thread_B (void *arg);
void* thread_C (void *arg);
void* thread_D (void *arg);

typedef struct sem_package {
    sem_t* sem1;
    sem_t* sem2;
} sem_package;

void* thread_A (void *arg) {
    sem_t* sem = (sem_t*) arg;

    int random_sleep = (int) (500 + ((random()) / RAND_MAX) * 2000);
    struct timespec sleep_time;
    sleep_time.tv_sec = random_sleep / 1000;
    sleep_time.tv_nsec = (random_sleep % 1000) * 1000000;

    nanosleep(&sleep_time, NULL);

    sem_post(sem);  //unblock B
    printf("\nA\n");
    return NULL;
}

void* thread_B (void *arg) {
    sem_package* pack = (sem_package*) arg;

    int random_sleep = (int) (500 + ((random()) / RAND_MAX) * 2000);
    struct timespec sleep_time;
    sleep_time.tv_sec = random_sleep / 1000;
    sleep_time.tv_nsec = (random_sleep % 1000) * 1000000;

    nanosleep(&sleep_time, NULL);

    sem_wait(pack->sem1);   //wait for A
    sem_post(pack->sem2);   //unblock C
    printf("\nB\n");
    return NULL;    
}

void* thread_C (void *arg) {
    sem_package* pack = (sem_package*) arg;

    int random_sleep = (int) (500 + ((random()) / RAND_MAX) * 2000);
    struct timespec sleep_time;
    sleep_time.tv_sec = random_sleep / 1000;
    sleep_time.tv_nsec = (random_sleep % 1000) * 1000000;

    nanosleep(&sleep_time, NULL);

    sem_wait(pack->sem2);   //wait for B
    sem_post(pack->sem1);   //unblock D
    printf("\nC\n");
    return NULL;
}

void* thread_D (void *arg) {
    sem_t* sem = (sem_t*) arg;

    int random_sleep = (int) (500 + ((random()) / RAND_MAX) * 2000);
    struct timespec sleep_time;
    sleep_time.tv_sec = random_sleep / 1000;
    sleep_time.tv_nsec = (random_sleep % 1000) * 1000000;

    nanosleep(&sleep_time, NULL);

    sem_wait(sem);  //wait for C
    printf("\nD\n");

    return NULL;
}

int main () {
    srandom((unsigned int) time(NULL)); 

    pthread_t threadA, threadB, threadC, threadD;

    sem_t sem_A_B;
    sem_t sem_C_D;
    sem_t sem_B_C;

    sem_init(&sem_A_B,0,0);
    sem_init(&sem_C_D,0,0);
    sem_init(&sem_B_C,0,0);

    struct sem_package pack1;
    pack1.sem1=&sem_A_B;
    pack1.sem2=&sem_B_C;

    struct sem_package pack2;
    pack2.sem1=&sem_C_D;
    pack2.sem2=&sem_B_C;

    pthread_create(&threadA,NULL,thread_A,&sem_A_B);
    pthread_create(&threadB,NULL,thread_B,&pack1);
    pthread_create(&threadC,NULL,thread_C,&pack2);
    pthread_create(&threadD,NULL,thread_D,&sem_C_D);

    long m;
    pthread_join(threadD, (void **) &m);

    sem_destroy(&sem_A_B);
    sem_destroy(&sem_B_C);
    sem_destroy(&sem_C_D);

    pthread_detach(threadA);
    pthread_detach(threadB);
    pthread_detach(threadC);

    return 0;
}

【问题讨论】:

  • “有时它的顺序错误”。你怎么知道的?
  • @n.m.因为函数中的 printf 。是的,它是真的,可能会发生一个 sem_post 并且在它可能会失去轮到他并且另一个线程可能会因为该帖子而开始工作
  • 一个,多线程的重要技巧是设计这样的事情不重要。 @n.m。已经指出要确保这样的事情是多么困难。最好不要费心尝试。
  • printf 未以任何方式同步,输出可以按任何顺序显示。
  • sem_wait() 之后和sem_post() 之前打印加上fflush() 会给你一个不错的机会。

标签: c pthreads


【解决方案1】:

comments,我注意到:

这与线程完成的顺序问题无关,但有一个错误是您没有将线程名称添加到struct sem_package,这样您就可以只有一个线程函数它需要从传入的参数中获取的所有信息。您需要做一些清理工作,但这会大大减少您的代码。

我也suggested:

sem_wait() 之后和sem_post() 之前加上fflush() 将给你一个不错的机会[以正确的顺序打印线程终止消息]。

Mucocommented

[我] 完全按照你说的做了,现在总是按照相同的顺序。我不明白为什么,但它有效。

而我responded:

之所以有效,是因为 (1) 线程在收到前任的“开始”信号之前无法打印,如果它们有前任的话,并且 (2) 一次只能打印一个线程,因为它们当他们有权限去并且在他们授予下一个线程权限之前打印。

我提到了不使用 fflush() 的可能性,但这似乎是个坏主意(在 Mac OS X 10.11.4 上使用 GCC 6.1.0 进行实验)。我还没有弄清楚为什么需要fflush() 调用(但请参阅下面的“始终检查错误”部分)。

这里有一些代码实现了一个为所有人服务的线程函数。它使用 C99 特性(指定的初始化程序)来初始化时间结构。顶部的 #pragma 抑制了有关在 Mac OS X 上弃用 sem_init()sem_destroy() 的警告(有关详细信息,请参阅 Why are sem_init(), sem_getvalue(), sem_destroy() deprecated on Mac OS X — and what replaces them?)。

/* Pragma needed on Mac OS X to suppress warnings about sem_init() and sem_destroy() */
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>

static void *thread_function(void *arg);

typedef struct sem_package
{
    sem_t *sem1;
    sem_t *sem2;
    char  *name;
} sem_package;

static
void *thread_function(void *arg)
{
    sem_package *pack = (sem_package *)arg;

    int random_sleep = (int)(500 + ((random()) / RAND_MAX) * 2000);
    struct timespec sleep_time = { .tv_sec  = random_sleep / 1000,
                                   .tv_nsec = (random_sleep % 1000) * 1000000
                                 };

    nanosleep(&sleep_time, NULL);

    if (pack->sem1)
        sem_wait(pack->sem1);   // wait for predecessor
    printf("\n%s\n", pack->name);
    fflush(stdout);
    if (pack->sem2)
        sem_post(pack->sem2);   // unblock successor
    return NULL;
}

int main(void)
{
    srandom((unsigned int)time(NULL));

    pthread_t threadA, threadB, threadC, threadD;

    sem_t sem_A_B;
    sem_t sem_C_D;
    sem_t sem_B_C;

    sem_init(&sem_A_B, 0, 0);
    sem_init(&sem_C_D, 0, 0);
    sem_init(&sem_B_C, 0, 0);

    struct sem_package pack1 = { NULL,     &sem_A_B, "A" };
    struct sem_package pack2 = { &sem_A_B, &sem_B_C, "B" };
    struct sem_package pack3 = { &sem_B_C, &sem_C_D, "C" };
    struct sem_package pack4 = { &sem_C_D, NULL,     "D" };

    pthread_create(&threadA, NULL, thread_function, &pack1);
    pthread_create(&threadB, NULL, thread_function, &pack2);
    pthread_create(&threadC, NULL, thread_function, &pack3);
    pthread_create(&threadD, NULL, thread_function, &pack4);

    void *vp;
    pthread_join(threadD, &vp);

    sem_destroy(&sem_A_B);
    sem_destroy(&sem_B_C);
    sem_destroy(&sem_C_D);

    pthread_detach(threadA);
    pthread_detach(threadB);
    pthread_detach(threadC);

    printf("\nAll done\n\n");
    return 0;
}

样本输出:

A

B

C

D

All done

始终检查错误

为了弄清楚为什么我在没有fflush() 的情况下会看到不稳定的行为,我在系统调用上添加了相对全面的错误检查。最初的运行是一个清醒的提醒,为什么检查系统调用的返回值很重要:

$ ./pthread-37
pthread-37: sem_init(): error (78) Function not implemented
$

坦率地说,我宁愿系统不提供“我会假装它在这里,但它不是真的在这里”的入口点。对于“已弃用”来说,这也是一个奇怪的含义。通常,这意味着“它存在(并且有效),但将来可能会丢失”。但是,对于信号量似乎没有执行命令的原因,至少有一个可靠的解释——它们不存在,因此它们无法执行命令。

所有这些都适用于 Mac。当我在带有 GCC 4.8.4 的 Ubuntu 14.04 LTS VM 中尝试它时,代码可以正常工作——即使有错误检查并且没有 fflush() 调用。这是理智的行为。

实物课程:

  • 检查系统调用的返回值。
  • Mac OS X 没有实现sem_init()

【讨论】:

  • 您在printf 之后移动了sem_post。随着这一变化,fflush 将不再需要,
  • @EmployedRussian:我同意,但是……当我在 Mac 上运行没有fflush() 的代码时,我得到了输出A B D C All done(分布在多行上)。这可能表明 Mac OS X 的 pthread 实现存在缺陷,但根据经验,这是我在没有fflush() 的情况下第一次(重新)运行时得到的。正如我所说,我不明白为什么fflush() 是必要的,但它似乎是必要的。
  • @JonathanLeffler 昨天我只尝试了flush,但现在我也尝试了不使用flush()。它也适用于我在 ubuntu 上没有 flush()。也感谢下面的代码。
  • @EmployedRussian:查看我的更新——Mac OS X 有一个sem_init() 的虚拟入口点,它只是简单地返回“未实现”作为错误。不是很有帮助。
【解决方案2】:

我希望它们以 A、B、C、D 的顺序结束。

您必须更准确地理解您的意思。考虑以下可能的操作交错(时间从上到下增加):

Thread A                      Thread B
sem_post(sem); //unblock B
                              sem_wait(pack->sem1);   //wait for A
                              sem_post(pack->sem2);   //unblock C
                              printf("\nB\n");
                              return NULL;    
printf("\nA\n");
return NULL;

如果发生上述交错,您将在A 之前打印B,并在线程A 之前打印线程B“结束”(到达return)。

现在考虑另一个交错:

Thread A                      Thread B
sem_post(sem); //unblock B
                              sem_wait(pack->sem1);   //wait for A
                              sem_post(pack->sem2);   //unblock C
                              printf("\nB\n");
printf("\nA\n");
return NULL;
                              return NULL;    

这里仍然A 之前打印了B,但现在线程A 在线程B 之前“结束”。您还可以在 B 之前打印 A,线程 A 在线程 B 之前或之后结束(我将交错留给您作为练习)。

那么答案是什么?

您可能感兴趣的不是“线程 A 在线程 B 之前结束”,而是“线程 A 在线程 B 之前结束其有用工作”。如果我们将有用的工作定义为打印AB,那么要解决您的问题,您必须在唤醒另一个线程之前移动printf 调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多