C信号量的奇怪优先级行为

3

我正在使用C语言进行并发编程的练习,在使用信号量时遇到了一些问题。 我在MacOSX上使用Xcode 6.3.2。

以下是一个似乎表现异常的示例程序:该示例的目的是打印ABCD或BACD字符串。

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

void *thread1(void*);
void *thread2(void*);

sem_t *sem0, *sem1, *sem2;;

int main(int argc, const char * argv[]) {

    pthread_t t1, t2;

    sem0 = sem_open("sem0", O_CREAT, 0600, 2);
    sem1 = sem_open("sem1", O_CREAT, 0600, 0);
    sem2 = sem_open("sem2", O_CREAT, 0600, 0);

    // quick check
    if (sem0 == SEM_FAILED || sem1 == SEM_FAILED || sem2 == SEM_FAILED) {
        printf("Something went wrong\n");
        return 0;
    }

    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    sem_close(sem0);
    sem_close(sem1);
    sem_close(sem2);

    return 0;
}

void *thread1(void* arg) {
    int n=0;
    while (n<10) {
        sem_wait(sem0);
        printf("A");
        fflush(stdout);
        sem_wait(sem1);
        printf("C");
        fflush(stdout);
        sem_post(sem2);
        n++;
    }
    pthread_exit(NULL);
}

void *thread2(void* arg) {
    int n=0;
    while (n<10) {
        sem_wait(sem0);
        printf("B");
        fflush(stdout);
        sem_post(sem1);
        sem_wait(sem2);
        printf("D\n");
        fflush(stdout);
        sem_post(sem0);
        sem_post(sem0);
        n++;
    }
    pthread_exit(NULL);
}

如果我正确地实现了信号量,那么结果应该是ABCD或BACD,但实际上我得到了各种奇怪的输出。以下是其中一部分输出:
ABCD
BAD
CABCD
BAD
CBAD
CBAD
CBAD
CBAD
CBAD
CBAD
C

有人能帮助我吗?提前感谢。

重要编辑:

我下载了Ubuntu,代码在那里运行非常顺畅,没有任何问题。所以,总结一下:

  • MacOSX 10.10.3 with Xcode 6.3.2 --> 不能正常工作
  • Ubuntu 15.04 --> 正常工作

不知道为什么。


@CrApHer 你知道它是否返回了错误,如果是的话,对于SEM_FAILED信号量参数,sem_wait的行为是什么? - Walter Delevich
1
你的线程函数签名有误。它们应该接受一个类型为 void * 的参数,即使它们不使用它。实际上,你的编译器应该会警告指针类型不匹配。 - John Bollinger
@Ivano 这是您第一次运行程序时发生的,还是在后续调用中发生的? - Walter Delevich
不要仅仅调用 sem_wait(),尝试使用 while (sem_wait(&sem) == -1 && errno == EINTR); 来确保没有任何东西在中断你的等待(出于某种原因)。 - Michael Burr
@MichaelBurr 尝试过了,问题仍然存在。我正在下载Ubuntu以便在Linux环境下测试代码,这是最后一次尝试让代码正常工作。 - Ivano
显示剩余5条评论
3个回答

2
你的问题可能是由于printf输出被缓存并在线程之间共享。你需要在使用信号量之前确保使用fflush清空缓冲区。

在我之前的一个程序中,使用了 mutexcondition,我也发现 printf() 输出混乱,在添加了 fflush(stdout) 之后,控制台的输出变得完全正确,所以我认为这个答案对于正确编写并发部分的 OP 来说绝对是正确的。顺便说一下,fflush() 需要时间,在我的程序中它使线程切换的频率不同,或者使线程的执行更有序,所以在用 printf()fflush() 证明程序是正确的之后,只需将它们取消注释即可。 - Eric
我不确定我是否正确实现了你的更改,你可以在op中看到修改后的代码,但我一直得到奇怪的输出。我真的不知道为什么,这可能与我使用的是MacOsx而不是纯Linux有关吗? - Ivano

1
当您调用sem_open()函数时,传入的名称为"sem1",存在打字错误,应该是sem2
sem1 = sem_open("sem1", O_CREAT, 0600, 0);
sem2 = sem_open("sem1", O_CREAT, 0600, 0);
//               ^^^^

那么指针sem1sem2将引用同一个信号量。

感谢您的注意,我已经编辑了代码,但是奇怪的行为仍然存在着。Ubuntu 几乎已经下载完成了,我将作为最后的希望在那个环境中尝试运行。 - Ivano
我看到你也更新了示例输出 - 所以至少行为有所改变?你可以尝试在对printf("A");printf("B");的调用中包装一对flockfile(stdout)/funlockfile(stdout)调用(也许删除fflush()调用 - 我不认为它们是必要的)?根据我对POSIX的理解,printf()应该已经执行了等效操作,因此我不希望出现差异,但我认为这值得一试。 - Michael Burr

0

Printf正在访问一个资源,即标准输出文件描述符。您需要请求锁定以允许仅由一个线程访问stdout。否则,您可能会得到交错的文本。我不确定为什么您要创建三个信号量,但请考虑,如果您有多个阻止同一资源的信号量,则打开了该资源的多个窗口。如果您一次只关闭一个,则根本没有阻止该资源。

编辑: 举个例子。线程1锁定sem0,线程2等待。线程1解锁sem0,线程2可以继续。现在线程1锁定sem1。他们都在写标准输出。这意味着,即使遵守您的锁定,它们两个都被允许写入stdout。

为什么之前没有发生是因为线程争用。

编辑2: 我认为John是正确的,我的原始解释并没有涵盖您的问题。我无法重现您的输出,也无法想象出达到您的输出的路径。即使使用其他人提到的fflush示例,我仍然看不到如何使用您的信号量应该工作的方式来形成特定的字符串。我只能猜测:

  1. 你的信号量有命名,但你没有销毁它们。这意味着它们会在程序调用之间保留下来。如果你在恰当的时间结束程序,信号量的值可能处于你不期望的状态(如果信号量存在,它们会忽略sem_open的value参数)。也许这就是导致输出问题的原因。
  2. 你没有检查sem函数的返回值,它们可能会返回错误。对无效的信号量调用后续函数应该立即返回,从而使你的printf语句只受线程争用的影响。

1
代码的目的,多个信号量等等,是为了允许以任意顺序打印'A'和'B'(即交错打印),并始终按固定顺序后跟'C'和'D'。目前我不明白为什么提供的代码没有实现这一点。您的回答在一般情况下可以,但它没有充分解决问题中提出的特定场景。 - John Bollinger
谢谢你指出我没有关闭信号量,现在我已经解决了另一个奇怪的问题(由于没有正确关闭信号量,导致信号量的初始值被忽略)。我现在也正在检查信号量的返回值,正如您在编辑后的代码中所看到的,但是奇怪的问题仍然存在。 - Ivano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接