我的代码可以锁定(某个库的)每个函数,并且我想对其进行优化。给定功能A
和B
,我不介意与任何其他A
同时运行的任何A
或与任何其他B
同时运行的任何B
,但是同时A
不能运行任何B
正在运行,反之亦然。线程计数是动态的,由于无法控制的原因,我不得不为互斥量和条件变量(即PTHREAD_MUTEX_INITIALIZER
)使用静态分配。
我直觉,最有效的方法是两个条件变量。使用pthread_mutex_trylock
允许一个功能(即A
)并行运行,而另一个必须串行化。静态初始化的*trylock
实际上也是未定义的行为...
编辑:
也许是这样的?我不确定这是否:
可以更简单。毕竟,互斥量是使用信号量实现的,但是需要四个互斥量和两个条件变量来实现基本上是反信号量。
涵盖所有比赛条件。
是否“公平”(超出默认优先级和计划)?
static int countA = 0;
static int countB = 0;
static pthread_mutex_t lockCountA = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t lockCountB = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t lockA = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t lockB = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t condA = PTHREAD_COND_INITIALIZER;
static pthread_cond_t condB = PTHREAD_COND_INITIALIZER;
// for B(), just s/B/A/g
static void A(void) {
pthread_mutex_lock(&lockB);
while(countB)
pthread_cond_wait(&condB, &lockB);
pthread_mutex_lock(&lockCountA);
countA += 1;
pthread_mutex_unlock(&lockCountA)
doA();
pthread_mutex_lock(&lockCountA)
countA -= 1;
if countA == 0:
pthread_cond_signal(&condA);
mutex_unlock(&lockCountA)
mutex_unlock(&lockB);
}
最佳答案
您的解决方案具有竞争条件。考虑当countA
和countB
均为零,并且两个线程同时调用A()
和B()
时的情况。第一个线程锁lockB
,第二个线程锁lockA
都将其检查的计数视为零,然后继续增加其各自的计数并出错。
解决方案中的另一个问题是,它使用的pthread_cond_signal()
不一定会唤醒一个以上的等待线程,因此,如果有10个线程等待输入B()
,而只有一个线程正在运行A()
,则后一个线程完成时只有一个B()
线程可以保证继续执行,其他9个线程可以无限期等待。
它还不允许多个线程同时运行doA()
,因为lockB
是在该调用中保留的。
要解决第一个问题,您可以使用一个保护countA
和countB
的互斥锁(因为我们必须检查的共享状态涉及这两个变量的组合)。同时,您也可能只使用一个条件变量:等待条件变量的线程必须全部等待输入A()
或全部等待输入B()
,但是两者的混合是不可能。解决第二个问题只需要pthread_cond_broadcast()
。这导致简单得多:
static int countA = 0;
static int countB = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void A(void)
{
pthread_mutex_lock(&lock);
while (countB)
pthread_cond_wait(&cond, &lock);
countA++;
pthread_mutex_unlock(&lock);
do_A();
pthread_mutex_lock(&lock);
countA--;
if (!countA)
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&lock);
}
此解决方案是正确的,但不是“公平的”-如果有连续的线程流在执行
A()
(这样,在前一个线程完成并递减之前,一个新的线程进入并递增countA
),则线程将等待执行B()
将永远等待。在您的特定用途中,这可能不是问题-例如,如果您知道执行A()
的任何线程最终都将执行B()
,则饥饿情况最终必须解决。可以通过在有线程排队进入
A()
时阻止新条目进入B()
来改进系统,以防止出现这种饥饿现象:static int countA = 0;
static int countB = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int queuedA = 0;
static int queuedB = 0;
static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
void A(void)
{
pthread_mutex_lock(&lock);
while (queuedB)
pthread_cond_wait(&queue_cond, &lock);
while (countB)
{
queuedA++;
pthread_cond_wait(&cond, &lock);
queuedA--;
}
if (!queuedA)
pthread_cond_broadcast(&queue_cond);
countA++;
pthread_mutex_unlock(&lock);
do_A();
pthread_mutex_lock(&lock);
countA--;
if (!countA)
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&lock);
}
这可以防止饥饿,因为:
当
queuedB
中有任何线程在等待cond
时,B()
始终为非零;当
queuedB
非零时,没有线程可以递增countA
,因此,countA
必须最终达到零,并允许等待cond
的线程继续进行。当
countA
为零时,没有线程可以递增queuedB
,因此queuedB
必须最终达到零,并允许等待queue_cond
的线程继续进行。关于c - pthreads:排他+并发函数(反信号量),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50975956/