C信号量和线程之间的“屏障”

时间:2022-02-01 21:01:48

I'm trying to solve a problem our Operating Systems professor showed us from a previous exam to prepare for the next one.

我正在努力解决我们的操作系统教授给我们展示的前一个考试的问题,为下一个考试做准备。

The problem is to have two threads that execute concurrently and may complete in a different amount of time. After a particular thread is completed, it needs to block until the other thread is completed, then they may continue their execution.

问题是有两个线程同时执行,并且可以在不同的时间内完成。在一个特定的线程完成之后,它需要阻塞,直到其他线程完成,然后它们才能继续执行。

It seems conceptually simple to me, but my code isn't working the way I think it should.

它在概念上对我来说似乎很简单,但是我的代码并没有按照我认为的那样工作。

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

#define N 10

sem_t t_1_sem;
sem_t t_2_sem;

void *thread(void *vargp);
/* shared by both threads*/
struct {
    int count;
} thread_count;

int main() {
    pthread_t tid, tid1;

    thread_count.count = 0;

    sem_init(&t_1_sem, 0, 1);
    sem_init(&t_2_sem, 0, 1);
    printf("Hello from main thread! tid:%ld pid:%d\n", pthread_self(), getpid());
    pthread_create(&tid, NULL, thread, NULL);
    pthread_create(&tid1, NULL, thread, NULL);

    pthread_join(tid, NULL);
    pthread_join(tid1, NULL);

    exit(0);
}

void *thread(void *vargp) {
    int i, tid;

    int val, val2;
    sem_getvalue(&t_1_sem, &val);
    sem_getvalue(&t_2_sem, &val2);
    printf("initial value::: %d : %d\n", val, val2);


    tid = thread_count.count;
    thread_count.count += 1;

    for(i = 0;i<N;i++){
        printf("%d, %d\n", tid, i);
        fflush(stdout);
        //sleep(0.1);
    }

    // TODO
    // barrier
    sem_getvalue(&t_1_sem, &val);
    sem_getvalue(&t_2_sem, &val2);
    printf("second value::: %d : %d\n", val, val2);
    int sem_val;
    if(tid == 0){
        // free other
        sem_getvalue(&t_1_sem, &sem_val);
        printf("posting to 2, waiting on 1 w/ %d count\n", sem_val);
        sem_post(&t_2_sem);
        // wait on this one
        sem_wait(&t_1_sem);
        printf("done waiting on 1\n");
    } else if(tid == 1){
        sem_getvalue(&t_2_sem, &sem_val);
        printf("posting to 1, waiting on 2 w/ %d count\n", sem_val);
        sem_post(&t_1_sem);
        sem_wait(&t_2_sem);
        printf("done waiting on 2\n");
    }

    sem_getvalue(&t_1_sem, &val);
    sem_getvalue(&t_2_sem, &val2);
    printf("final value::: %d : %d\n", val, val2);
    return NULL;
}

What I'm expecting to see is both of the threads counting til 10, then the two "final value" printf's happening next to each other. However, what I'm seeing is the "final value" prints occurring immediately after the thread finishes counting to 10 - it doesn't seem to wait.

我期望看到的是两个线程都计数到10,然后两个“最终值”printf在一起。然而,我看到的是在线程计数到10之后立即出现的“最终值”打印——它似乎没有等待。

I'm also getting really weird values for the sem_val integer I print in the "posting to N" printf's, e.g.:

我在printf's中输出的sem_val整数的值也很奇怪,例如:

Hello from main thread! tid:-1606277344 pid:5479
initial value::: 0 : 0
0, 0
initial value::: 0 : 0
1, 0
0, 1
1, 1
0, 2
1, 2
1, 3
1, 4
1, 5
0, 3
1, 6
0, 4
1, 7
0, 5
1, 8
0, 6
1, 9
0, 7
second value::: 0 : 0
posting to 1, waiting on 2 w/ -1809628646 count
0, 8
done waiting on 2
final value::: 0 : 0
0, 9
second value::: 0 : 0
posting to 2, waiting on 1 w/ -1809628646 count
done waiting on 1
final value::: 0 : 0

Any ideas/hints?

有什么想法/提示吗?

7 个解决方案

#1


6  

sem_getvalue() is not implemented on osx. http://discussions.apple.com/thread.jspa?messageID=9404131&tstart=0

sem_getvalue()在osx上没有实现。http://discussions.apple.com/thread.jspa?messageID=9404131&tstart=0

#2


7  

You may want to consult The Little Book of Semaphores. Section 3.5 describes the Barrier pattern and how it is correctly implemented.

你可能想咨询一下有关信号量的小册子。第3.5节描述了Barrier模式以及如何正确实现它。

I know that doesn't directly answer your question, but it should point you in the right direction.

我知道这并不能直接回答你的问题,但它应该为你指明正确的方向。

#3


1  

Is this what you want?

这就是你想要的吗?

0, 0
0, 1  
0, 2
0, 3
0, 4
0, 5
0, 6
0, 7
0, 8
0, 9
1, 0
1, 1
1, 2
1, 3
1, 4
1, 5
1, 6
1, 7
1, 8
1, 9

I am not sure I understand your idea but I suspect you might use the semaphore incorrectly. Below is the code that generate above phenomena. Hopefully ,it has something useful for your problem.

我不确定我理解你的想法,但我怀疑你可能用错了信号量。下面是生成上述现象的代码。希望它对你的问题有帮助。

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

#define N 10

sem_t t_1_sem;
sem_t t_2_sem;

void *thread_1(void *vargp);
void *thread_2(void *vargp);
/* shared by both threads*/
struct {
    int count;
} thread_count;

 int main() {
pthread_t tid, tid1;

thread_count.count = 0;

sem_init(&t_1_sem, 0, 1);
sem_init(&t_2_sem, 0, 1);
printf("Hello from main thread! tid:%ld pid:%d\n", pthread_self(), getpid());
pthread_create(&tid, NULL, thread_1, NULL);
pthread_create(&tid1, NULL, thread_2, NULL);

pthread_join(tid, NULL);
pthread_join(tid1, NULL);

exit(0);
}

void *thread_1(void *vargp) {
int i, tid;

int val, val2;
sem_getvalue(&t_1_sem, &val);
printf("enter thread_1\n");
sem_wait(&t_1_sem);
//even thread_1 is sleeping , thread_2 will not be scheduled to run
//as thread_1 is holding the semphore
sleep(1);
tid = thread_count.count;
thread_count.count += 1;

for(i = 0;i<N;i++){
    printf("%d, %d\n", tid, i);
    fflush(stdout);
    //sleep(0.1);
}

    sem_post(&t_1_sem);
    return NULL;
}


void *thread_2(void *vargp) {
int i, tid;

int val, val2;
sem_getvalue(&t_1_sem, &val);

printf("enter thread_2\n");
sem_wait(&t_1_sem);
tid = thread_count.count;
thread_count.count += 1;

for(i = 0;i<N;i++){
    printf("%d, %d\n", tid, i);
    fflush(stdout);
    //sleep(0.1);
}

    sem_post(&t_1_sem);
    return NULL;
}

#4


1  

I just successfully dealt with something like this using pthreads with mutexes and condition variables.

我刚刚用带互斥体和条件变量的pthreads成功地处理了类似的事情。

Semaphores are a general inter-thread or inter-process messaging mechanism, which you can incidentally use for thread coordination with some difficulty. Mutexes are specialized binary semaphores aimed squarely at thread coordination, and offer a simplified API to do that.

信号量是一种通用的线程间或进程间消息传递机制,您可以顺便将其用于某些困难的线程协调。互斥体是专门针对线程协调的二进制信号量,并提供了一个简化的API来实现这一点。

So if you're not constrained to use semaphores, you might consider mutexes as an easier way to get the job done.

因此,如果您没有限制使用信号量,您可能会认为互斥是一种更容易完成任务的方法。

Whichever thread coordination approach you pick, use a search engine to find code samples that solve similar problems, and make sure you understand them. For instance, "pthread semaphore example" and "pthread mutex example" both got lots of interesting hits.

无论选择哪种线程协调方法,使用搜索引擎查找解决类似问题的代码示例,并确保理解它们。例如,“pthread信号量示例”和“pthread互斥示例”都获得了许多有趣的结果。

A big hint: make sure you actually need two semaphores. One semaphore or mutex can control an arbitrarily large number of threads.

一个大提示:确保你确实需要两个信号量。一个信号量或互斥量可以控制任意数量的线程。

As a more general pedagogical remark, not aimed at your specific example, threading is one of those places where the notion of KISS really applies. It's very easy to get too fancy and get yourself all tangled up.

作为一种更普通的教育性的评论,而不是针对你的具体例子,线程是一个真正适用于KISS概念的地方。人们很容易变得太花哨,把自己搞得一团糟。

#5


0  

This doesn't exactly answer your question, but you might want to look at extracting the barrier from the thread - if it was C# you'd make it an object, and I've hardly done any C, but you'd probably have a struct and a couple of functions. This may or may not help a lot in this situation, but it saves writing a barrier from scratch each time you want one. Also, if you first implement a semaphore, you can then write a barrier in terms of semaphores, which simplifies things. (I'm currently doing a subject on concurrent programming in .NET, and one of the things we're doing is writing a set of concurrency utilities - semaphore, mutex, barrier, rendezvous, channel etc. - which we can then plug in to other programs.)

这并不能确切地回答您的问题,但是您可能想看看如何从线程中提取barrier——如果它是c#,那么您将使它成为一个对象,而我几乎没有做过任何C,但是您可能有一个struct和几个函数。在这种情况下,这可能有帮助,也可能没有帮助,但它可以在每次需要时从头开始编写障碍。另外,如果您首先实现了一个信号量,那么您就可以用信号量来写一个障碍,这简化了事情。(我目前正在做一个关于。net并发编程的课题,我们正在做的一件事是编写一组并发实用程序——信号量、互斥量、障碍、会合、通道等),然后我们可以将其插入到其他程序中。

As for the barrier, as James has mentioned already, The Little Book of Semaphores describes a correct implementation

至于障碍,正如詹姆斯已经提到的,小书中的信号量描述了一个正确的实现

#6


0  

this is that what you want:

这就是你想要的:

i'm 0 and waiting for 1 - starting
i'm 1 and waiting for 0 - starting
i'm 1, 0 arrived, lets go
i'm 0, 1 arrived, lets go
i'm 1 and waiting for 0 - stopping
i'm 0 and waiting for 1 - stopping
i'm 0, 1 stopped, go home now
i'm 1, 0 stopped, go home now

and the correct code, found what's wrong of your original code yourself.

而正确的代码,发现了你原来代码的错误。

#define SEM_INIT_V      0

static sem_t t_0_sem;
static sem_t t_1_sem;

void *thread(void *vargp);
/* shared by both threads*/
struct {
        int count;
} thread_count = {};

int main() {
        pthread_t tid0, tid1;

        thread_count.count = 0;

        sem_init(&t_0_sem, 0, SEM_INIT_V);
        sem_init(&t_1_sem, 0, SEM_INIT_V);
        pthread_create(&tid0, NULL, thread, &thread_count.count);
        thread_count.count++;
        pthread_create(&tid1, NULL, thread, &thread_count.count);

        pthread_join(tid0, NULL);
        pthread_join(tid1, NULL);

        return 0;
}

void *thread(void *vargp) {
        int tid = *(int*)vargp;

        //await to sync 0 & 1
        if (0 == tid) {
                puts("i'm 0 and waiting for 1 - starting");
                sem_post(&t_1_sem);
                sem_wait(&t_0_sem);
                puts("i'm 0, 1 arrived, lets go");
                sleep(8);
        } else {
                puts("i'm 1 and waiting for 0 - starting");
                sem_post(&t_0_sem);
                sem_wait(&t_1_sem);
                puts("i'm 1, 0 arrived, lets go");
                sleep(3);
        }

        if(tid == 0){
                puts("i'm 0 and waiting for 1 - stopping");
                sem_post(&t_1_sem);
                sem_wait(&t_0_sem);
                puts("i'm 0, 1 stopped, go home now");
        } else if(tid == 1){
                puts("i'm 1 and waiting for 0 - stopping");
                sem_post(&t_0_sem);
                sem_wait(&t_1_sem);
                puts("i'm 1, 0 stopped, go home now");
        }

        return NULL;
}

#7


0  

You are initializing the semaphores with initial value of 1 so the wait will succeed immediately (the posts at end of the operation will simply bump the value to 2).

您正在初始化初始值为1的信号量,因此等待将立即成功(操作末尾的post将直接将值增加到2)。

sem_init(&t_1_sem, 0, 1);
sem_init(&t_2_sem, 0, 1);

Then second problem is that you are generating the tid variable in way which is not a thread safe. Both threads might end with value of zero even if that did not happen in this case.

第二个问题是生成tid变量的方式不是线程安全的。即使在这种情况下没有发生,两个线程都可能以值为0结束。

#1


6  

sem_getvalue() is not implemented on osx. http://discussions.apple.com/thread.jspa?messageID=9404131&tstart=0

sem_getvalue()在osx上没有实现。http://discussions.apple.com/thread.jspa?messageID=9404131&tstart=0

#2


7  

You may want to consult The Little Book of Semaphores. Section 3.5 describes the Barrier pattern and how it is correctly implemented.

你可能想咨询一下有关信号量的小册子。第3.5节描述了Barrier模式以及如何正确实现它。

I know that doesn't directly answer your question, but it should point you in the right direction.

我知道这并不能直接回答你的问题,但它应该为你指明正确的方向。

#3


1  

Is this what you want?

这就是你想要的吗?

0, 0
0, 1  
0, 2
0, 3
0, 4
0, 5
0, 6
0, 7
0, 8
0, 9
1, 0
1, 1
1, 2
1, 3
1, 4
1, 5
1, 6
1, 7
1, 8
1, 9

I am not sure I understand your idea but I suspect you might use the semaphore incorrectly. Below is the code that generate above phenomena. Hopefully ,it has something useful for your problem.

我不确定我理解你的想法,但我怀疑你可能用错了信号量。下面是生成上述现象的代码。希望它对你的问题有帮助。

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

#define N 10

sem_t t_1_sem;
sem_t t_2_sem;

void *thread_1(void *vargp);
void *thread_2(void *vargp);
/* shared by both threads*/
struct {
    int count;
} thread_count;

 int main() {
pthread_t tid, tid1;

thread_count.count = 0;

sem_init(&t_1_sem, 0, 1);
sem_init(&t_2_sem, 0, 1);
printf("Hello from main thread! tid:%ld pid:%d\n", pthread_self(), getpid());
pthread_create(&tid, NULL, thread_1, NULL);
pthread_create(&tid1, NULL, thread_2, NULL);

pthread_join(tid, NULL);
pthread_join(tid1, NULL);

exit(0);
}

void *thread_1(void *vargp) {
int i, tid;

int val, val2;
sem_getvalue(&t_1_sem, &val);
printf("enter thread_1\n");
sem_wait(&t_1_sem);
//even thread_1 is sleeping , thread_2 will not be scheduled to run
//as thread_1 is holding the semphore
sleep(1);
tid = thread_count.count;
thread_count.count += 1;

for(i = 0;i<N;i++){
    printf("%d, %d\n", tid, i);
    fflush(stdout);
    //sleep(0.1);
}

    sem_post(&t_1_sem);
    return NULL;
}


void *thread_2(void *vargp) {
int i, tid;

int val, val2;
sem_getvalue(&t_1_sem, &val);

printf("enter thread_2\n");
sem_wait(&t_1_sem);
tid = thread_count.count;
thread_count.count += 1;

for(i = 0;i<N;i++){
    printf("%d, %d\n", tid, i);
    fflush(stdout);
    //sleep(0.1);
}

    sem_post(&t_1_sem);
    return NULL;
}

#4


1  

I just successfully dealt with something like this using pthreads with mutexes and condition variables.

我刚刚用带互斥体和条件变量的pthreads成功地处理了类似的事情。

Semaphores are a general inter-thread or inter-process messaging mechanism, which you can incidentally use for thread coordination with some difficulty. Mutexes are specialized binary semaphores aimed squarely at thread coordination, and offer a simplified API to do that.

信号量是一种通用的线程间或进程间消息传递机制,您可以顺便将其用于某些困难的线程协调。互斥体是专门针对线程协调的二进制信号量,并提供了一个简化的API来实现这一点。

So if you're not constrained to use semaphores, you might consider mutexes as an easier way to get the job done.

因此,如果您没有限制使用信号量,您可能会认为互斥是一种更容易完成任务的方法。

Whichever thread coordination approach you pick, use a search engine to find code samples that solve similar problems, and make sure you understand them. For instance, "pthread semaphore example" and "pthread mutex example" both got lots of interesting hits.

无论选择哪种线程协调方法,使用搜索引擎查找解决类似问题的代码示例,并确保理解它们。例如,“pthread信号量示例”和“pthread互斥示例”都获得了许多有趣的结果。

A big hint: make sure you actually need two semaphores. One semaphore or mutex can control an arbitrarily large number of threads.

一个大提示:确保你确实需要两个信号量。一个信号量或互斥量可以控制任意数量的线程。

As a more general pedagogical remark, not aimed at your specific example, threading is one of those places where the notion of KISS really applies. It's very easy to get too fancy and get yourself all tangled up.

作为一种更普通的教育性的评论,而不是针对你的具体例子,线程是一个真正适用于KISS概念的地方。人们很容易变得太花哨,把自己搞得一团糟。

#5


0  

This doesn't exactly answer your question, but you might want to look at extracting the barrier from the thread - if it was C# you'd make it an object, and I've hardly done any C, but you'd probably have a struct and a couple of functions. This may or may not help a lot in this situation, but it saves writing a barrier from scratch each time you want one. Also, if you first implement a semaphore, you can then write a barrier in terms of semaphores, which simplifies things. (I'm currently doing a subject on concurrent programming in .NET, and one of the things we're doing is writing a set of concurrency utilities - semaphore, mutex, barrier, rendezvous, channel etc. - which we can then plug in to other programs.)

这并不能确切地回答您的问题,但是您可能想看看如何从线程中提取barrier——如果它是c#,那么您将使它成为一个对象,而我几乎没有做过任何C,但是您可能有一个struct和几个函数。在这种情况下,这可能有帮助,也可能没有帮助,但它可以在每次需要时从头开始编写障碍。另外,如果您首先实现了一个信号量,那么您就可以用信号量来写一个障碍,这简化了事情。(我目前正在做一个关于。net并发编程的课题,我们正在做的一件事是编写一组并发实用程序——信号量、互斥量、障碍、会合、通道等),然后我们可以将其插入到其他程序中。

As for the barrier, as James has mentioned already, The Little Book of Semaphores describes a correct implementation

至于障碍,正如詹姆斯已经提到的,小书中的信号量描述了一个正确的实现

#6


0  

this is that what you want:

这就是你想要的:

i'm 0 and waiting for 1 - starting
i'm 1 and waiting for 0 - starting
i'm 1, 0 arrived, lets go
i'm 0, 1 arrived, lets go
i'm 1 and waiting for 0 - stopping
i'm 0 and waiting for 1 - stopping
i'm 0, 1 stopped, go home now
i'm 1, 0 stopped, go home now

and the correct code, found what's wrong of your original code yourself.

而正确的代码,发现了你原来代码的错误。

#define SEM_INIT_V      0

static sem_t t_0_sem;
static sem_t t_1_sem;

void *thread(void *vargp);
/* shared by both threads*/
struct {
        int count;
} thread_count = {};

int main() {
        pthread_t tid0, tid1;

        thread_count.count = 0;

        sem_init(&t_0_sem, 0, SEM_INIT_V);
        sem_init(&t_1_sem, 0, SEM_INIT_V);
        pthread_create(&tid0, NULL, thread, &thread_count.count);
        thread_count.count++;
        pthread_create(&tid1, NULL, thread, &thread_count.count);

        pthread_join(tid0, NULL);
        pthread_join(tid1, NULL);

        return 0;
}

void *thread(void *vargp) {
        int tid = *(int*)vargp;

        //await to sync 0 & 1
        if (0 == tid) {
                puts("i'm 0 and waiting for 1 - starting");
                sem_post(&t_1_sem);
                sem_wait(&t_0_sem);
                puts("i'm 0, 1 arrived, lets go");
                sleep(8);
        } else {
                puts("i'm 1 and waiting for 0 - starting");
                sem_post(&t_0_sem);
                sem_wait(&t_1_sem);
                puts("i'm 1, 0 arrived, lets go");
                sleep(3);
        }

        if(tid == 0){
                puts("i'm 0 and waiting for 1 - stopping");
                sem_post(&t_1_sem);
                sem_wait(&t_0_sem);
                puts("i'm 0, 1 stopped, go home now");
        } else if(tid == 1){
                puts("i'm 1 and waiting for 0 - stopping");
                sem_post(&t_0_sem);
                sem_wait(&t_1_sem);
                puts("i'm 1, 0 stopped, go home now");
        }

        return NULL;
}

#7


0  

You are initializing the semaphores with initial value of 1 so the wait will succeed immediately (the posts at end of the operation will simply bump the value to 2).

您正在初始化初始值为1的信号量,因此等待将立即成功(操作末尾的post将直接将值增加到2)。

sem_init(&t_1_sem, 0, 1);
sem_init(&t_2_sem, 0, 1);

Then second problem is that you are generating the tid variable in way which is not a thread safe. Both threads might end with value of zero even if that did not happen in this case.

第二个问题是生成tid变量的方式不是线程安全的。即使在这种情况下没有发生,两个线程都可能以值为0结束。