0

我遇到的问题是:我的“父母”经历了一个从 0 到 4 的 for 循环。在每次迭代中,我有 3 个线程要打印出它们的 12 个数字的“段”(0 到11)。

例如,当父循环为 0 时,线程 0 会打印出 0,线程 1 会打印出 4,线程 2 会打印出 8。

当父循环为 1 时,线程 0 会打印出 1,线程 1 会打印出 5,线程 2 会打印出 9。

所以理想的输出是 0 1 2 3 4 5 6 7 8 9 10 11 。我知道您无法确定线程何时运行,因此不会完全按照该顺序运行,但我至少希望在线程打印 3 4 或 5 之前打印 0 1 和 2。

这就是我遇到的问题。似乎一个或另一个线程被留在尘土中并且没有打印出它的片段,或者没有完全打印出它的片段以及其他线程。这是我正在尝试完全理解信号量的问题。

我有两个信号量,一个在线程工作时阻塞父级(生产者?),另一个阻塞每个线程,直到生产者增加到下一个索引。我认为通过这种方式我可以强迫他们在继续之前等待对方完成,但由于某种原因我遇到了问题。

这是我的代码:

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

#define maxNum 12

sem_t parentSem;
sem_t threadSem;
int finishedThreads;
int done = 0;
int stuff[12];
int i;

void* threadFunction(int m){
    int printNum;
    int baseNum;
    //Determine the value the thread should start at
    baseNum = (double)(maxNum/3) * m;
    while(!done){ //ensure thread doesn't exit before parent is done with whole loop
        //wait for parent to increment
        sem_wait(&threadSem);
        printNum = baseNum + i;

            //keep track of how many threads are finished to let parent continue
        finishedThreads++;
        if(finishedThreads == 3){
                    //let parent continue if all threads are finished
            sem_post(&parentSem);
        }
    }

}

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

    sem_init(&parentSem, 0, 1);
    sem_init(&threadSem, 0, 0);
    int rc;
    pthread_t threads[3];
    int l; 
    for(l = 0; l < 12; l++){
        stuff[l] = l;
    }
    int j;
    for(j = 0; j < 3; j++){
        rc = pthread_create(&threads[i], NULL, threadFunction, (void*) j);

    }
    int k;
    for(i = 0; i < 4; i++){
        sem_wait(&parentSem); //wait for children here (initially sem set to 1)
        finishedThreads = 0; //set finished thread counter to 0
        for(k = 0; k < 3; k++){
            //increment thread semaphore to 3 so each thread can run
            sem_post(&threadSem);
        }
    }
    for(i = 1; i < 3; i++){
        pthread_join(threads[i], NULL);
    }



}

如何确保所有线程在父级递增之前运行?如何确保所有线程每“轮”运行一次而没有任何卡在后面?有时同一个线程运行两次而不是每个线程运行一次.....帮助?

非常感谢大家的帮助。

编辑:新代码状态:(线程函数)

while(!done){

    printf("Thread %d not done yet...\n", m);
    if(m == 0){
        sem_wait(&threadSem0);
    }else if(m == 1){
        sem_wait(&threadSem1);
    }else if(m == 2){
        sem_wait(&threadSem2);
    }
    printNum = baseNum + i;
    printf("Thread %d past waiting, number segment: %d\n", m, printNum);

    finishedThreads++;
    if(finishedThreads == 3){
        sem_post(&parentSem);
    }
}

父部分:

for(i = 0; i < 4; i++){
    printf("In parent for loop, counter: %d\n", i);
    printf("Parent past wait semaphore\n");
    finishedThreads = 0;
    if(i == 3) done = 1;
    sem_post(&threadSem0);
    sem_post(&threadSem1);
    sem_post(&threadSem2);
    sem_wait(&parentSem);
}
for(i = 1; i < 3; i++){
    pthread_join(threads[i], NULL);
}
4

2 回答 2

0

您需要访问finishedThreadsatomic 以避免每个线程仅使用缓存副本。您的程序在没有优化(*)的情况下编译是否可以工作?

在 C++ 中执行此操作的正确方法是使用std::atomic. 请参阅此问题的彻底答案。

更新:

如果线程快速循环(如您的情况),则对所有子线程使用一个信号量可能会产生问题。您必须为每个子线程使用一个信号量来解决这个问题。

sem_t threadSem[3];

// ...

sem_wait(&threadSem+m);

// ...

for (i = 0; i < 3; i++)
    sem_init(threadSem+i, 0, 0);

// ...

    for(k = 0; k < 3; k++){
        //increment each thread semaphore so each thread can run
        sem_post(threadSem+k);
    }

还有一些我早期没有发现的东西:

您使用变量i在 中进行增量threadFunction。这行不通。正确的公式应该只取决于m。由于每个线程都有自己的baseNumand副本printNum,因此您可以毫无问题地使用它们:

baseNum = (double)(maxNum/3) * m;
while(!done){ //ensure thread doesn't exit before parent is done with whole loop
    //wait for parent to increment
    sem_wait(&threadSem);
    printNum++;
    // ....

您的代码中有很多东西取决于子线程的数量,我建议为此使用一个常量(就像您为 所做的那样maxNum)。

您没有设置变量done来指示子线程的工作结束。

volatile int done;

// in threadFunction

 // wait for start
sem_wait(threadSem+m)
while(!done){ 
    // thread work here
    // ...
    //wait for parent to increment at the end of the loop
    sem_wait(threadSem+m);
}

// in main

for(k = 0; k < 3; k++){
    //increment each thread semaphore so each thread can run
    if (i == 4) done = 1;
    sem_post(threadSem+k);
}

当然done还必须遵循“volatile或互斥”规则。

您最后不会加入所有线程:

for(i = 0; i < 3; i++){
    pthread_join(threads[i], NULL);
}

(*) 如果您使用 gcc,我的意思是使用参数-O0,甚至使用-g.

于 2013-03-10T22:04:20.543 回答
0

你需要一个屏障,特别是一个可重复使用的屏障。查看 Allen Downey 的信号量小书,第 3.6.7 节,在此处复制(PDF)可能很有用。代码是用 Python 编写的,但它的要点很清楚。它需要一个互斥锁和两个计数信号量,在您的情况下,您有 4 个参与者在屏障中。

您可以通过设置线程优先级来控制哪个线程首先被唤醒,以便第一个线程是最重要的,依此类推。

于 2013-03-11T05:40:05.183 回答