线程永远不会获得锁(pthread_mutex_lock)

Amm*_*izi 1 c multithreading mutex locking pthreads

故事

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

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

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

  1. 主进程线程随时调用pthread_mutex_lock一个循环内。
  2. 主进程持有锁时,请求锁的线程会阻塞(等待授予锁)。
  3. 主进程用释放锁时pthread_mutex_unlock线程应该突然获得锁。
  4. 主进程再次请求锁时,主进程应该等待线程释放锁。

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

如何处理这种情况?

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

重现问题的简单代码

#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);
  }
}
Run Code Online (Sandbox Code Playgroud)

编译运行

gcc test.c -o test -lpthread
./test
Run Code Online (Sandbox Code Playgroud)

预期结果

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...
...
Run Code Online (Sandbox Code Playgroud)

实际结果

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...
...
Run Code Online (Sandbox Code Playgroud)

按顺序调用故事

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 ...
Run Code Online (Sandbox Code Playgroud)

概括

pthread_mutex_lock 不保证锁请求的顺序。

Ced*_*ric 5

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

如果您需要某个顺序,则可以选择使用条件变量。这样,您可以将标志设置为下一个应该获得互斥锁的成员。然后您可以等待互斥锁,直到该值符合预期。请参阅https://linux.die.net/man/3/pthread_cond_wait

或者,如果您的示例无论如何都在其中睡眠,那么您可以在 unlock() 调用后移动睡眠。虽然严格来说这不是保证,但它绝对可以为简单测试提供帮助。不过,我不建议将这种方法用于更严重/更复杂的事情。

编辑:正如 Shawn 正确添加的那样,如果您不关心它是哪个其他线程,您还可以使用pthread_yield( 1 ) 来允许另一个线程获取互斥锁。sched_yield(2)中描述了一些与屈服的复杂性。

PS:我会发表评论,但我的代表现在已经足够高了:)

  • @Shawn No. `pthread_yield()` [不可移植,因为它不再是 POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) 和 [无论如何都不能保证它会起作用](https: //man7.org/linux/man-pages/man2/sched_yield.2.html#注释)。如果您想要特定的订单,**您**必须自己编码。或者使用第三方库。 (4认同)
  • [关于 `pthread_yield()`/`sched_yield()` 的有趣线程](https://comp.programming.threads.narkive.com/iRYqp6xQ/posix-threads-pthread-yeld):“...`pthread_yield() ` 从来都不是标准的......真的,如果你依赖 `pthread_yield()` 来做任何事情,你可能会遇到逻辑问题。调度不是同步;它只是调度......” (2认同)
  • @Shawn **不,它根本不能保证**。来自同一个链接:“认为 pthread_yield() 只不过是一个“调度程序提示”,本质上是“哦,强大的调度程序,你是否应该以你的智慧认为,你可能会在不久的将来从我这里拿走我的处理器,我恳求你现在就做吧。“它可能会做,或者它可能会耸耸肩,继续做它的事情。你不知道,你的代码无法告诉你,” (2认同)
  • @Shawn,Naw,恰好可以工作,这意味着有一些系统状态(负载、中断率等)、调度参数会导致它失败。 (2认同)