3

我正在尝试解决我们的操作系统教授在之前的考试中向我们展示的一个问题,以便为下一个考试做准备。

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

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

#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;
}

我期望看到的是两个线程都计数到 10,然后两个“最终值”printf彼此相邻发生。但是,我看到的是线程完成计数到 10 后立即发生的“最终值”打印 - 它似乎没有等待。

sem_val对于我在“发布到 N”中打印的整数,我也得到了非常奇怪的值printf,例如:

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

任何想法/提示?

4

7 回答 7

7

您可能想咨询The Little Book of Semaphores。3.5 节描述了屏障模式以及它是如何正确实现的。

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

于 2009-10-15T01:49:56.267 回答
6

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

于 2009-10-15T12:59:46.333 回答
1

这是你想要的吗?

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

我不确定我是否理解您的想法,但我怀疑您可能错误地使用了信号量。下面是产生上述现象的代码。希望它对您的问题有用。

#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;
}
于 2009-10-15T02:21:00.097 回答
1

我刚刚使用带有互斥锁和条件变量的 pthread 成功地处理了类似的事情。

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

因此,如果您不受限制使用信号量,您可能会认为互斥锁是完成工作的一种更简单的方法。

无论您选择哪种线程协调方法,请使用搜索引擎查找解决类似问题的代码示例,并确保您理解它们。例如,“pthread semaphore example”和“pthread mutex example”都获得了很多有趣的点击。

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

作为更一般的教学评论,不是针对您的具体示例,线程是真正适用 KISS 概念的地方之一。很容易变得太花哨而让自己陷入困境。

于 2009-10-15T02:49:29.883 回答
0

这并不能完全回答你的问题,但你可能想看看从线程中提取障碍 - 如果它是 C# 你会把它变成一个对象,我几乎没有做过任何 C,但你可能有一个结构和几个函数。在这种情况下,这可能会或可能不会有很大帮助,但它可以节省每次您需要时从头开始编写屏障。此外,如果您首先实现一个信号量,那么您可以根据信号量编写一个屏障,这可以简化事情。(我目前正在研究 .NET 中的并发编程主题,我们正在做的一件事是编写一组并发实用程序 - 信号量、互斥量、屏障、集合点、通道等 - 然后我们可以将其插入到其他程序。)

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

于 2009-10-15T02:01:35.587 回答
0

这就是你想要的:

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

和正确的代码,自己发现原始代码有什么问题。

#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;
}
于 2009-10-15T03:01:36.970 回答
0

您正在使用初始值 1 初始化信号量,因此等待将立即成功(操作结束时的帖子将简单地将值增加到 2)。

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

然后第二个问题是您正在以非线程安全的方式生成tid变量。即使在这种情况下没有发生这种情况,两个线程也可能以零值结束。

于 2009-10-18T13:22:52.980 回答