控制线程结束的顺序

时间:2016-05-07 18:50:26

标签: c pthreads

我有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;
}

2 个答案:

答案 0 :(得分:3)

comments中,我注意到:

  

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

我也是suggested

  

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

Muco commented

  

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

responded

  

它之所以有效,是因为(1)线程无法打印,直到他们收到“去”为止。来自其前任的信号,如果他们有前任,并且(2)一次只能打印一个线程,因为他们在有权允许的情况下进行打印,并且在授予下一个线程许可之前进行打印。

我提到了不使用fflush()的可能性,但这似乎是一个坏主意(在Mac OS X 10.11.4上试验GCC 6.1.0)。我还没有弄清楚为什么fflush()来电是必要的(但请参阅&#39;始终检查错误&#39;以下部分)。

这里有一些代码实现了一个为所有人服务的线程函数。它使用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
$

坦率地说,我宁愿系统没有提供切入点,而且我会假装它在这里,但它并不是真的在这里#&## 34 ;.对于“弃用”而言,这是一个奇怪的含义。太;通常,这意味着&#34;它存在(并且有效)但未来可能会丢失&#34;。但是,至少有一个可靠的解释,为什么信号量似乎没有强制执行 - 他们不存在,所以他们不能执行命令。

这一切都适用于Mac。当我在使用GCC 4.8.4的Ubuntu 14.04 LTS VM中尝试它时,代码正常工作 - 即使错误检查和没有fflush()调用。这是理智的行为。

对象课程:

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

答案 1 :(得分:0)

  

我希望他们以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;

如果要进行上述交错,您将在B之前打印A,在线程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;    

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

那么答案是什么?

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