Multithreading 创建一个函数,该函数将一直阻塞,直到被n/2个以上的线程(伪代码)调用为止

Multithreading 创建一个函数,该函数将一直阻塞,直到被n/2个以上的线程(伪代码)调用为止,multithreading,operating-system,pthreads,mutex,semaphore,Multithreading,Operating System,Pthreads,Mutex,Semaphore,有n个线程。我试图实现一个函数(伪代码),如果它被线程调用,它将直接阻塞。如果超过n/2个线程调用了该函数,则每个线程都将被阻塞,并且该函数将停止阻塞线程。如果调用该函数的线程超过了n/2个,该函数将不再阻止其他线程,而是立即返回 我是这样做的,但我不确定我是否正确地完成了最后一部分,如果超过n/2线程调用它,函数将立即返回: (非常感谢伪代码,因为这样我就有更好的机会理解它了!:) int n = total amount of threads sem waiter = 0 sem mutex

n个
线程。我试图实现一个函数(伪代码),如果它被线程调用,它将直接阻塞。如果超过
n/2个
线程调用了该函数,则每个线程都将被阻塞,并且该函数将停止阻塞线程。如果调用该函数的线程超过了
n/2个
,该函数将不再阻止其他线程,而是立即返回

我是这样做的,但我不确定我是否正确地完成了最后一部分,如果超过
n/2
线程调用它,函数将立即返回:

(非常感谢伪代码,因为这样我就有更好的机会理解它了!:)

int n = total amount of threads
sem waiter = 0
sem mutex = 1
int counter = 0

function void barrier()
    int x
    P(mutex)
    if counter > n / 2 then
        V(mutex)
        for x = 0; x <= n / 2; x++;
            V(waiter)
        end for
    end if
    else
        counter++
        V(mutex)
        P(waiter)
    end else
end function
int n=线程总数
sem=0
sem互斥量=1
整数计数器=0
函数void barrier()
整数x
P(互斥)
如果计数器>n/2,则
V(互斥)

对于x=0;你所描述的是一个非重置屏障。Pthreads有一个屏障实现,但它是一种重置类型

要使用pthreads实现您想要的功能,您需要一个互斥体加上一个条件变量和一个共享计数器。进入函数的线程锁定互斥锁并检查计数器。如果还没有足够的其他线程到达,那么它将在CV上等待,否则它将向其广播以唤醒所有等待的线程。如果你愿意的话,你可以把它做成一条线,让它成为广播的标尺。例如:

struct my_barrier {
    pthread_mutex_t barrier_mutex;
    pthread_cond_t barrier_cv;
    int threads_to_await;
};

void barrier(struct my_barrier *b) {
    pthread_mutex_lock(&b->barrier_mutex);
    if (b->threads_to_await > 0) {
        if (--b->threads_to_await == 0) {
            pthread_cond_broadcast(&b->barrier_cv);
        } else {
            do {
                pthread_cond_wait(&b->barrier_cv, &b->barrier_mutex);
            } while (b->threads_to_await);
        }
    }
    pthread_mutex_unlock(&b->barrier_mutex);
}
更新:伪代码 或者,由于伪代码表示对您很重要,因此,在伪代码语言中,与问题中使用的语言类似的是:

int n = total amount of threads
mutex m
condition_variable cv
int to_wait_for = n / 2

function void barrier()
    lock(mutex)

    if to_wait_for == 1 then
        to_wait_for = 0
        broadcast(cv)
    else if to_wait_for > 1 then
        to_wait_for = to_wait_for - 1
        wait(cv)
    end if

    unlock(mutex)
end function
这比伪代码稍微高一点,因为它不假设互斥体是作为信号量实现的。(对于您标记的pthreads,您需要pthreads互斥体,而不是信号量,才能使用pthreads条件变量)。它还省略了真正的C代码的细节,这些代码处理从等待条件变量和初始化互斥和cv的错误唤醒。此外,它将变量表示为所有全局变量——这样的函数在实践中可以通过这种方式实现,但形式很差


还请注意,它假设pthreads语义用于条件变量:等待cv将临时释放互斥锁,允许其他线程锁定互斥锁,但等待cv的线程将在自己通过等待之前重新获取互斥锁。

我在回答中做出的一些假设:

  • P(…)
    类似于
    sem\u wait(…)
  • V(…)
    类似于
    sem\u post(…)
  • 屏障无法重置

我不确定我是否正确地完成了最后一部分,如果超过
n/2个
线程调用该函数,该函数将立即返回

伪代码在大部分情况下应该可以正常工作,但早期返回/退出条件可以得到显著改善

一些问题(但不是什么大问题):

  • 第一次满足条件
    counter>n/2
    时,向
    服务员发出信号(即
    V(…)
    (n/2)+1次(因为它是从
    0
    n/2
    ),而不是
    n/2
    (这也是
    counter
    当时的值)
  • 第一次遇到
    计数器>n/2
    之后的每个后续调用也将向
    服务员发出信号(即
    V(…)
    )并向另一个
    (n/2)+1次
    。相反,它应该提前返回,而不是重新发出信号
这些问题可以通过一些小的调整来解决

int n = total count of threads
sem mutex = 1;
sem waiter = 0;
int counter = 0;
bool released = FALSE;

function void barrier() {
    P(mutex)
    // instead of the `released` flag, could be replaced with the condition `counter > n / 2 + 1`
    if released then
        // ensure the mutex is released prior to returning
        V(mutex)
        return
    end if

    if counter > n / 2 then
        // more than n/2 threads have tried to wait, mark barrier as released
        released = TRUE
        // mutex can be released at this point, as any thread acquiring `mutex` after will see that `release` is TRUE and early return
        V(mutex)
        // release all blocked threads; counter is guaranteed to never be incremeneted again
        int x
        for x = 0; x < counter; x++
            V(waiter)
        end for
    else
        counter++
        V(mutex)

        P(waiter)
    end else
}
int n=线程总数
sem互斥=1;
sem=0;
int计数器=0;
bool released=FALSE;
函数void barrier(){
P(互斥)
//代替'released'标志,可替换为'counter>n/2+1条件`
如果当时被释放
//确保在返回之前释放互斥锁
V(互斥)
返回
如果结束
如果计数器>n/2,则
//超过n/2个线程尝试等待,将屏障标记为已释放
释放=真
//此时可以释放互斥体,因为任何在这之后获取“互斥体”的线程都会看到“release”为TRUE,并且会提前返回
V(互斥)
//释放所有阻塞的线程;保证计数器不再递增
整数x
对于x=0;x<计数器;x++
V(服务员)
结束
其他的
柜台++
V(互斥)
P(服务员)
结束其他
}

在实际的C实现中,标记变量
volatile
不会阻止涉及它们的数据争用。看见在使用时,您的
发布的
需要原子语义,而伪代码或随附的散文并不能清楚地传达这些语义。@JohnBollinger
volatile
并不是为了解决伪代码中的数据争用,但表示i)应从关键部分的主存中重新获取值,ii)指令不应重新排序。所有变量都在关键部分中使用-如果变量已经通过
mutex
锁防止并行执行,则不需要原子语义。唯一的例外是第一次
释放
检查是提前返回,而不必获取
互斥锁
锁,这可能会失败,但通过关键部分中的另一项检查恢复。@JohnBollinger决定需要等待的线程T在释放互斥体之前,用
计数器+
保留一个释放。如果另一个线程穿过屏障并释放所有线程,它将增加信号量
计数器
的次数(该线程在关键部分的前面增加),从而允许线程T获取
并继续,而不管是否