c-线程从不获取锁(pthread_mutex_lock)



故事

根据手册页https://linux.die.net/man/3/pthread_mutex_lock

互斥对象引用的互斥对象应通过调用pthread_mutex_lock((来锁定。如果互斥锁已经被锁定,那么调用线程将阻塞,直到互斥锁可用。

我有一个带线程的程序。以下是程序流程:

  1. 主进程线程总是在循环内部调用pthread_mutex_lock
  2. 主进程持有锁时,请求锁的线程会阻塞(等待授予锁(
  3. 主进程使用pthread_mutex_unlock释放锁时,线程应该会突然获得锁
  4. 主进程再次请求锁定时,主要进程程释放锁定

问题是,在第3点,线程不会在主进程释放锁后立即获得锁。当主进程在下一个循环周期(在第4点(调用pthread_mutex_lock时,它首先得到它。

如何应对这种情况?

问题

如何使线程主进程释放锁后立即获得锁?

重现问题的简单代码

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
void *
my_thread(void *p)
{
(void)p;
while (1) {
pthread_mutex_lock(&my_mutex);
printf("The thread is holding the lock...n");
sleep(1);
pthread_mutex_unlock(&my_mutex);
}
}
int
main()
{
pthread_t t;
pthread_create(&t, NULL, my_thread, NULL);
pthread_detach(t);
while (1) {
pthread_mutex_lock(&my_mutex);
printf("The main process is holding the lock...n");
sleep(1);
pthread_mutex_unlock(&my_mutex);
}
}

编译并运行

gcc test.c -o test -lpthread
./test

预期结果

The main process is holding the lock...
The thread is holding the lock...
The main process is holding the lock...
The thread is holding the lock...
The main process is holding the lock...
The thread is holding the lock...
The main process is holding the lock...
...

实际结果

The main process is holding the lock...
The main process is holding the lock...
The main process is holding the lock...
The main process is holding the lock...
The main process is holding the lock...
The main process is holding the lock...
The main process is holding the lock...
...

按顺序调用故事

main   -> [1] call lock (get the lock)
thread -> [2] call lock (waiting for main to unlock)
main   -> [3] call unlock
thread -> [4] (still does not get the lock from [2], why? even though it has been unlocked?)
main   -> [5] lock (get the lock again)
thread -> [6] (still does not get the lock from [2])
main   -> [7] call unlock
thread -> [8] (still does not get the lock from [2], why? even though it has been unlocked?)
main   -> [9] lock (get the lock again)
... and so on ...

摘要

pthread_mutex_lock不保证锁请求的顺序。

pthread_mutex_lock保证它将锁定,直到互斥对象可用。这并不意味着每个lock((调用都进入一个队列,并保证下一个得到互斥锁。这只意味着没有其他人会同时拥有锁。

如果您需要特定的顺序,则可以选择使用条件变量。这样,就可以为下一个应该获得互斥对象的成员设置一个标志。然后,您可以等待互斥锁,直到值达到预期值。看见https://linux.die.net/man/3/pthread_cond_wait.

或者,如果您的示例如上所述有睡眠,您可以在unlock((调用之后移动睡眠。虽然严格来说这不是一个保证,但对于一个简单的测试来说,它肯定会奏效。不过,对于更严重/更复杂的情况,我不建议使用这种方法。

EDIT:正如Shawn正确添加的那样,如果您不在乎是哪个线程,您也可以使用pthread_yield(1(允许另一个线程获取互斥。sched_feld(2(中描述了一些复杂的让步。

附言:我会评论,但我的代表现在已经足够高了:(

这里有一个"fairlock"示例;你可以做得更好:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct fairlock FairLock;
struct fairlock {
pthread_mutex_t lock;
pthread_cond_t  cv;
long scount;
long wcount;
};

#define err(x,v) do { int t; if ((t=(x)) != (v)) { 
error(__FILE__,__LINE__, #x, t, (v)); 
}} while (0)
static void error(char *fn, int lno, char *s, long x, long v) {
fprintf(stderr, "%s:%d %s returned %ld rather than %ldn",
fn, lno, s, x, v);
exit(1);
}

void Lock(FairLock *f) {
err(pthread_mutex_lock(&f->lock), 0);
long me = f->scount++;
while (f->wcount != me) {
err(pthread_cond_wait(&f->cv, &f->lock), 0);
}
err(pthread_mutex_unlock(&f->lock), 0);
}
void UnLock(FairLock *f) {
err(pthread_mutex_lock(&f->lock), 0);
if (f->scount > f->wcount) {
f->wcount++;
err(pthread_cond_broadcast(&f->cv), 0);
}
err(pthread_mutex_unlock(&f->lock), 0);
}
FairLock *NewLock(void) {
FairLock *p = malloc(sizeof *p);
if (p != 0) {
err(pthread_mutex_init(&p->lock, 0),0);
err(pthread_cond_init(&p->cv, 0),0);
p->scount = p->wcount = 0;
}
return p;
}
void DoneLock(FairLock *f) {
err(pthread_mutex_destroy(&f->lock), 0);
err(pthread_cond_destroy(&f->cv), 0);
}

并且您的testlock.c更改为使用它;同样,还有改进的空间,但你应该能够在任何地方坚持睡觉,这将是公平的。。。。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "fairlock.c"
FairLock *my;
void *
my_thread(void *p)
{
while (1) {
Lock(my);
printf("%s is holding the lock...n", p);
UnLock(my);
}
}

int
main()
{
pthread_t t;
my = NewLock();
pthread_create(&t, NULL, my_thread, "one");
pthread_detach(t);
pthread_create(&t, NULL, my_thread, "two");
pthread_detach(t);
pthread_create(&t, NULL, my_thread, "three");
pthread_detach(t);
pthread_create(&t, NULL, my_thread, "four");
pthread_detach(t);
pthread_create(&t, NULL, my_thread, "five");
pthread_detach(t);
while (1) {
Lock(my);
printf("main process is holding the lock...n");
UnLock(my);
}
}

TLDR版本:

释放锁后,两个循环中的任何一个所做的下一件事就是再次尝试获取它。

当刚刚释放锁的线程a和已经被阻塞的线程B在等待锁时发生竞争时,线程a几乎总是会获胜,因为线程a已经在运行,而线程B仍然是"锁";睡着了">

释放锁并不是"立即"的;唤醒";等待线程。它所做的只是将另一线程的状态从"0"改变为"0";等待锁定";至";等待被分配一个CPU运行;不久之后,调度器将在另一个CPU上恢复线程B的上下文,线程B将开始运行,但那时已经太晚了。线程A已经重新锁定了锁。

最新更新