C使用信号量和线程打印乒乓

时间:2015-04-09 03:57:57

标签: c multithreading semaphore

我不确定我是否理解信号量和线程所以我决定尝试一个相对简单的例子。我试图有2个线程可以交替打印,一个打印“ping”另一个打印“pong”,每个打印通知另一个是通过使用信号量完成的。但是当我实现下面的代码时,它会打印几百次ping,然后轻轻地停顿几百次。

#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
sem_t pingsem;

void ping(){
    printf("Ping started\n");
    while(1){
        sem_wait(&pingsem);
        printf("ping\n");
        sem_post(&pingsem);
    }
}

void pong(){
    printf("Pong started\n");
    while(1){
        sem_wait(&pingsem);
        printf("pong\n");
        sleep(1);
        sem_post(&pingsem);
    }
}

int main(){
    sem_destroy(&pingsem);  //make sure the semaphore starts dead
    sem_init(&pingsem, 0, 1);  //initialize semaphore
    pthread_t ping_thread, pong_thread;  //start the threading
    pthread_create(&ping_thread, NULL, ping, NULL);
    pthread_create(&pong_thread, NULL, pong, NULL);
    pthread_join(ping_thread,NULL);
    pthread_join(pong_thread,NULL);

    return 0;
}

我使用编译:

gcc stest.c -o stest -lpthread -lrt

没有错误或警告,但是当我运行它时,我得到:

$ ./stest
Ping started
ping
ping
ping
ping
Pong started
ping
ping
.
. hundreds of pings
.
ping
ping
ping
pong
pong
pong
pong
.
. hundreds of pongs
.

它最终会关闭,但为什么线程不会交替打印每一个?

5 个答案:

答案 0 :(得分:5)

你的例子中显示的问题是,这是一场比赛,因为两种阅读都没有有效阻止另一方。两个线程都以调度程序允许的方式运行。它的编码方式,每个线程可以在其时间片期间多次自由运行(循环),并满足自己的信号量测试。在具有典型调度的多核/多CPU系统上,两个线程都可以同时运行并且在某种程度上任意地逐步执行。

这是一个有效的ping-ponging线程示例,它使用免费的信号量来创建你想要的乒乓互锁。

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

sem_t pingsem, pongsem;

void *
ping(void *arg) 
{
    for (;;) {
        sem_wait(&pingsem);
        printf("ping\n");
        sem_post(&pongsem);
    }
}

void *
pong(void *arg) 
{
    for (;;) {
        sem_wait(&pongsem);
        printf("pong\n");
        sem_post(&pingsem);
    }
}

int 
main(void) 
{
    sem_init(&pingsem, 0, 0);
    sem_init(&pongsem, 0, 1);
    pthread_t ping_thread, pong_thread; 
    pthread_create(&ping_thread, NULL, ping, NULL);
    pthread_create(&pong_thread, NULL, pong, NULL);
    pthread_join(ping_thread, NULL);
    pthread_join(pong_thread, NULL);
    return 0;
}

答案 1 :(得分:4)

因为当你创建一个线程时,它会在操作系统说出来后立即执行! 所以你创建了两个这样的线程:

pthread_create(&ping_thread, NULL, ping, NULL);
// scheduler interrupt from OS
pthread_create(&pong_thread, NULL, pong, NULL);

这很好,但操作系统看到了第一个新线程并运行它直到它的时间片耗尽。只有这样,主线程才能获得足够长的控制权以创建下一个线程。

至于为什么他们不交替是一个不同的问题!你看到线程同步有多难?你有这个代码:

while(1){
    sem_wait(&pingsem);
    printf("ping\n");
    sem_post(&pingsem);
}

但是你将信号量初始化为值1.所以sem_wait递减为0,然后打印一条消息,然后递增回1.没问题,对吧?

sem_post和后续(下一个循环)sem_wait之间的延迟只有1条指令,跳回到循环的开头。因此,除非偶然,操作系统在sem_post之后中断线程,但在sem_wait之前,单个线程将继续自己打印。

答案 2 :(得分:1)

你需要两个信号量,'pingsem'和'pongsem'。将1初始化为1,将另一个初始化为零。然后:

ping_thread:

while(true){
  wait(pingsem);
  doWork();
  send(pongsem);
}

pong_thread:

while(true){
  wait(pongsem);
  doWork();
  send(pingsem);
}

初始化为一个信号量的一个单元然后充当工作令牌,并在线程之间来回发出信号。只有带令牌的线程可以完成工作,另一个必须等​​到它获得令牌。

答案 3 :(得分:-2)

詹姆斯·史密斯有正确的答案..既然你在再次抓住你的锁之前没有安排出来,你会发现在线程完成之前它几乎每次都能重新获得锁定这是时间片。

如果要强制调度以便另一个可以运行,可以尝试sleep(0)或sched_yield()。这将强制调度,以便如果另一个线程正在等待运行它。这样更有可能看到你的ping,pong,ping,pong。但仍然不能保证并且完全依赖于您的OS调度程序(并且可能仅适用于具有相同优先级的两个线程的单个核心系统)。

试试这个:(就像我说的......没有保证..但很有可能它会起作用)

void ping(){
    printf("Ping started\n");
    while(1){
        sem_wait(&pingsem);
        printf("ping\n");
        sem_post(&pingsem);
        sched_yield(); // Schedule out.
    }
}

void pong(){
    printf("Pong started\n");
    while(1){
        sem_wait(&pingsem);
        printf("pong\n");
        sleep(1);
        sem_post(&pingsem);
        sched_yield(); // Schedule out.
    }
}

编辑:更改为sched_yield()

答案 4 :(得分:-2)

其他答案是正确的,我只是发布将按您的意愿运行的代码(无论如何在OSX 10.10.3上)

#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
sem_t pingsem;

void ping(){
    printf("Ping started\n");
    while (1) {
        sem_wait(&pingsem);
        printf("ping\n");
        sem_post(&pingsem);
    }
}

void pong(){
    printf("Pong started\n");
    while (1) {
        sem_wait(&pingsem);
        printf("pong\n");
        sem_post(&pingsem);
    }
}

int main(){
    sem_destroy(&pingsem);  //make sure the semaphore starts dead
    sem_init(&pingsem, 0, 1);  //initialize semaphore
    pthread_t ping_thread, pong_thread;  //start the threading
    pthread_create(&ping_thread, NULL, ping, (void *)0);
    pthread_create(&pong_thread, NULL, pong, (void *)1);


    pthread_exit(NULL);

    return 0;
}