线程永远不会获得锁 (pthread_mutex_lock)
Thread never gets the lock (pthread_mutex_lock)
故事
根据手册页 https://linux.die.net/man/3/pthread_mutex_lock
The mutex object referenced by mutex shall be locked by calling pthread_mutex_lock(). If the mutex is already locked, the calling thread shall block until the mutex becomes available.
我有一个带线程的程序。这是程序流程:
- 主进程和线程总是在循环中调用
pthread_mutex_lock
。
- 当主进程持有锁时,请求锁的线程阻塞(等待锁被授予).
- 当主进程用
pthread_mutex_unlock
释放锁时,线程应该会突然拿到锁
- 当主进程再次请求锁时,主进程应该等待线程 释放锁。
问题在于,在第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() 调用之后移动休眠。虽然这不是严格意义上的保证,但它绝对可以解决简单测试的问题。不过,我不建议将此方法用于更多 serious/complex。
编辑:正如肖恩正确添加的那样,您也可以使用pthread_yield
(1) to allow another thread to acquire the mutex if you don't care which other thread it is. Some intricacies with yielding are described in sched_yield(2).
PS:我会发表评论,但我的代表现在已经足够高了:)
这里以'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 %ld\n",
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已经运行ning,并且线程 B 仍然处于“睡眠状态”。
释放锁不会立即“唤醒”等待线程。它所做的只是将另一个线程的状态从“等待锁定”更改为“等待分配一个 CPU 到 运行 on”。不久之后的某个时间,调度程序将着手在另一个 CPU 上恢复线程 B 的上下文,并且线程 B 将启动 运行ning,但到那时为时已晚。线程 A 将已经重新锁定了锁。
故事
根据手册页 https://linux.die.net/man/3/pthread_mutex_lock
The mutex object referenced by mutex shall be locked by calling pthread_mutex_lock(). If the mutex is already locked, the calling thread shall block until the mutex becomes available.
我有一个带线程的程序。这是程序流程:
- 主进程和线程总是在循环中调用
pthread_mutex_lock
。 - 当主进程持有锁时,请求锁的线程阻塞(等待锁被授予).
- 当主进程用
pthread_mutex_unlock
释放锁时,线程应该会突然拿到锁 - 当主进程再次请求锁时,主进程应该等待线程 释放锁。
问题在于,在第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() 调用之后移动休眠。虽然这不是严格意义上的保证,但它绝对可以解决简单测试的问题。不过,我不建议将此方法用于更多 serious/complex。
编辑:正如肖恩正确添加的那样,您也可以使用pthread_yield
(1) to allow another thread to acquire the mutex if you don't care which other thread it is. Some intricacies with yielding are described in sched_yield(2).
PS:我会发表评论,但我的代表现在已经足够高了:)
这里以'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 %ld\n",
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已经运行ning,并且线程 B 仍然处于“睡眠状态”。
释放锁不会立即“唤醒”等待线程。它所做的只是将另一个线程的状态从“等待锁定”更改为“等待分配一个 CPU 到 运行 on”。不久之后的某个时间,调度程序将着手在另一个 CPU 上恢复线程 B 的上下文,并且线程 B 将启动 运行ning,但到那时为时已晚。线程 A 将已经重新锁定了锁。