在具有 4 个线程的程序上调用 pthread_cond_signal 时,同一个线程获得互斥锁
When calling pthread_cond_signal on program with 4 threads, the same thread gets the mutex
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t node[4];
pthread_mutex_t token;
pthread_cond_t cond;
int id=0;
void *func(int n)
{
int count = 0;
while (count < 10){
pthread_mutex_lock(&token);
while (id != n){
printf("Whoops not my turn, id=%d\n",n);
pthread_cond_wait(&cond, &token);}
//if (id == n){
count += 1;
printf ("My turn! id= %d\n",n);
printf("count %d\n", count);
if (id == 3){
id = 0;}
else{
id += 1;}
//}else{
// printf("Not my turn! id=%d\n",n);}
// pthread_mutex_unlock(&token);
// sleep(2);}
pthread_mutex_unlock(&token);
pthread_cond_signal(&cond);}
printf ("ID=%d has finished\n",n);
return(NULL);
}
int main()
{
int i;
pthread_mutex_init(&token,NULL);
pthread_cond_init(&cond,NULL);
for(i=0;i<4;i++)
pthread_create(&node[i],NULL,(void *)func,(void *)i);
for(i=0;i<4;i++)
pthread_join(node[i],NULL);
pthread_mutex_destroy(&token);
return 0;
}
这是我的代码,它是一个使用线程的 C 程序。我认为这里不需要知道它的目的,但我将举例说明我遇到的问题。
假设 id 为 1 的线程(由 func 中的 n 定义)获得互斥锁,returns "My turn! id=1"。当调用mutex_unlock和cond_signal时,下一个获取互斥量的线程实际上又是id为1的线程,它会打印"Whoops not my turn, id=1"。只有这样,id 为 2 的线程才会获得互斥锁,然后打印 "My turn! id=2",但是当 id = 2 的线程之后将获得互斥锁。这是我的程序输出:
Whoops not my turn, id=1
Whoops not my turn, id=2
Whoops not my turn, id=3
My turn! id= 0
count 1
Whoops not my turn, id=0
My turn! id= 1
count 1
Whoops not my turn, id=1
My turn! id= 2
count 1
Whoops not my turn, id=2
My turn! id= 3
count 1
Whoops not my turn, id=3
My turn! id= 0
count 2
Whoops not my turn, id=0
My turn! id= 1
count 2
Whoops not my turn, id=1
My turn! id= 2
count 2
Whoops not my turn, id=2
My turn! id= 3
count 2
Whoops not my turn, id=3
My turn! id= 0
count 3
Whoops not my turn, id=0
My turn! id= 1
count 3
Whoops not my turn, id=1
My turn! id= 2
count 3
Whoops not my turn, id=2
My turn! id= 3
count 3
Whoops not my turn, id=3
My turn! id= 0
count 4
Whoops not my turn, id=0
My turn! id= 1
count 4
Whoops not my turn, id=1
My turn! id= 2
count 4
Whoops not my turn, id=2
My turn! id= 3
count 4
Whoops not my turn, id=3
My turn! id= 0
count 5
Whoops not my turn, id=0
My turn! id= 1
count 5
Whoops not my turn, id=1
My turn! id= 2
count 5
Whoops not my turn, id=2
My turn! id= 3
count 5
Whoops not my turn, id=3
My turn! id= 0
count 6
Whoops not my turn, id=0
My turn! id= 1
count 6
Whoops not my turn, id=1
My turn! id= 2
count 6
Whoops not my turn, id=2
My turn! id= 3
count 6
Whoops not my turn, id=3
My turn! id= 0
count 7
Whoops not my turn, id=0
My turn! id= 1
count 7
Whoops not my turn, id=1
My turn! id= 2
count 7
Whoops not my turn, id=2
My turn! id= 3
count 7
Whoops not my turn, id=3
My turn! id= 0
count 8
Whoops not my turn, id=0
My turn! id= 1
count 8
Whoops not my turn, id=1
My turn! id= 2
count 8
Whoops not my turn, id=2
My turn! id= 3
count 8
Whoops not my turn, id=3
My turn! id= 0
count 9
Whoops not my turn, id=0
My turn! id= 1
count 9
Whoops not my turn, id=1
My turn! id= 2
count 9
Whoops not my turn, id=2
My turn! id= 3
count 9
Whoops not my turn, id=3
My turn! id= 0
count 10
ID=0 has finished
My turn! id= 1
count 10
ID=1 has finished
My turn! id= 2
count 10
ID=2 has finished
My turn! id= 3
count 10
ID=3 has finished
如您所见,在每次成功之后,线程打印 "My turn!",之后它将获得互斥量并导致 "Whoops not my turn!"。我不明白为什么在我调用 pthread_cond_signal 时会发生这种情况,它应该在当前线程可以重新获取互斥锁之前向另一个线程发出唤醒信号。请帮我找到这个解决方案,因为我认为我缺少一些重要的东西。如果我的解释不足,请随时向我询问更多信息。非常感谢您的宝贵时间!
在 Linux 的情况下,一般情况下可能 posix 系统,不能保证互斥锁请求得到服务的顺序。当互斥量解锁时,OS 不会强制上下文切换,因此当前 运行 线程继续其循环并再次锁定互斥量。我不知道您是否可以通过 pthread 接口更改线程优先级,但如果可能的话,您可以提高线程“(n)%4”的优先级,当线程“(n)%4”运行时,它会将其优先级设置回正常并将线程“(n+1)%4”设置为更高的优先级。
在 Windows 使用其本机互斥锁的情况下,显然锁定请求的顺序在队列或等效项中进行跟踪,因此当当前 运行 线程循环回锁定请求时, Windows 将切换到互斥体的锁定请求队列中的第一个线程。我不知道这是否被记录在案,但我已经确认它是这样工作的。我不知道 Windows 中的 pthreads mutex 是否会以这种方式工作。
一种可能的替代方法是每个线程一个互斥锁,但我不知道这是否会导致任何问题,具体取决于 OS。每个线程使用一个信号量应该可以工作
旁注,如果使用条件变量,您可能会获得虚假唤醒,并且需要处理这种情况。
https://en.wikipedia.org/wiki/Spurious_wakeup
但是,如果使用本机 Windows 同步类型,如互斥量、信号量...,则不会发生虚假唤醒。
对于一些OS,比如Linux,这些问题已经足够了,一些高端多处理/多线程应用程序安装内核级驱动程序来实现内核时间自旋锁可以避免这些问题。
@rcgldr 已经对所涉及的时间进行了很好的解释。如果您想增加给另一个线程机会的几率,请尝试添加对 pthread_yield 的调用,这应该使调度程序有机会选择不同的线程,尽管这也不能保证。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t node[4];
pthread_mutex_t token;
pthread_cond_t cond;
int id=0;
void *func(int n)
{
int count = 0;
while (count < 10){
pthread_mutex_lock(&token);
while (id != n){
printf("Whoops not my turn, id=%d\n",n);
pthread_cond_wait(&cond, &token);}
//if (id == n){
count += 1;
printf ("My turn! id= %d\n",n);
printf("count %d\n", count);
if (id == 3){
id = 0;}
else{
id += 1;}
//}else{
// printf("Not my turn! id=%d\n",n);}
// pthread_mutex_unlock(&token);
// sleep(2);}
pthread_mutex_unlock(&token);
pthread_cond_signal(&cond);}
printf ("ID=%d has finished\n",n);
return(NULL);
}
int main()
{
int i;
pthread_mutex_init(&token,NULL);
pthread_cond_init(&cond,NULL);
for(i=0;i<4;i++)
pthread_create(&node[i],NULL,(void *)func,(void *)i);
for(i=0;i<4;i++)
pthread_join(node[i],NULL);
pthread_mutex_destroy(&token);
return 0;
}
这是我的代码,它是一个使用线程的 C 程序。我认为这里不需要知道它的目的,但我将举例说明我遇到的问题。
假设 id 为 1 的线程(由 func 中的 n 定义)获得互斥锁,returns "My turn! id=1"。当调用mutex_unlock和cond_signal时,下一个获取互斥量的线程实际上又是id为1的线程,它会打印"Whoops not my turn, id=1"。只有这样,id 为 2 的线程才会获得互斥锁,然后打印 "My turn! id=2",但是当 id = 2 的线程之后将获得互斥锁。这是我的程序输出:
Whoops not my turn, id=1
Whoops not my turn, id=2
Whoops not my turn, id=3
My turn! id= 0
count 1
Whoops not my turn, id=0
My turn! id= 1
count 1
Whoops not my turn, id=1
My turn! id= 2
count 1
Whoops not my turn, id=2
My turn! id= 3
count 1
Whoops not my turn, id=3
My turn! id= 0
count 2
Whoops not my turn, id=0
My turn! id= 1
count 2
Whoops not my turn, id=1
My turn! id= 2
count 2
Whoops not my turn, id=2
My turn! id= 3
count 2
Whoops not my turn, id=3
My turn! id= 0
count 3
Whoops not my turn, id=0
My turn! id= 1
count 3
Whoops not my turn, id=1
My turn! id= 2
count 3
Whoops not my turn, id=2
My turn! id= 3
count 3
Whoops not my turn, id=3
My turn! id= 0
count 4
Whoops not my turn, id=0
My turn! id= 1
count 4
Whoops not my turn, id=1
My turn! id= 2
count 4
Whoops not my turn, id=2
My turn! id= 3
count 4
Whoops not my turn, id=3
My turn! id= 0
count 5
Whoops not my turn, id=0
My turn! id= 1
count 5
Whoops not my turn, id=1
My turn! id= 2
count 5
Whoops not my turn, id=2
My turn! id= 3
count 5
Whoops not my turn, id=3
My turn! id= 0
count 6
Whoops not my turn, id=0
My turn! id= 1
count 6
Whoops not my turn, id=1
My turn! id= 2
count 6
Whoops not my turn, id=2
My turn! id= 3
count 6
Whoops not my turn, id=3
My turn! id= 0
count 7
Whoops not my turn, id=0
My turn! id= 1
count 7
Whoops not my turn, id=1
My turn! id= 2
count 7
Whoops not my turn, id=2
My turn! id= 3
count 7
Whoops not my turn, id=3
My turn! id= 0
count 8
Whoops not my turn, id=0
My turn! id= 1
count 8
Whoops not my turn, id=1
My turn! id= 2
count 8
Whoops not my turn, id=2
My turn! id= 3
count 8
Whoops not my turn, id=3
My turn! id= 0
count 9
Whoops not my turn, id=0
My turn! id= 1
count 9
Whoops not my turn, id=1
My turn! id= 2
count 9
Whoops not my turn, id=2
My turn! id= 3
count 9
Whoops not my turn, id=3
My turn! id= 0
count 10
ID=0 has finished
My turn! id= 1
count 10
ID=1 has finished
My turn! id= 2
count 10
ID=2 has finished
My turn! id= 3
count 10
ID=3 has finished
如您所见,在每次成功之后,线程打印 "My turn!",之后它将获得互斥量并导致 "Whoops not my turn!"。我不明白为什么在我调用 pthread_cond_signal 时会发生这种情况,它应该在当前线程可以重新获取互斥锁之前向另一个线程发出唤醒信号。请帮我找到这个解决方案,因为我认为我缺少一些重要的东西。如果我的解释不足,请随时向我询问更多信息。非常感谢您的宝贵时间!
在 Linux 的情况下,一般情况下可能 posix 系统,不能保证互斥锁请求得到服务的顺序。当互斥量解锁时,OS 不会强制上下文切换,因此当前 运行 线程继续其循环并再次锁定互斥量。我不知道您是否可以通过 pthread 接口更改线程优先级,但如果可能的话,您可以提高线程“(n)%4”的优先级,当线程“(n)%4”运行时,它会将其优先级设置回正常并将线程“(n+1)%4”设置为更高的优先级。
在 Windows 使用其本机互斥锁的情况下,显然锁定请求的顺序在队列或等效项中进行跟踪,因此当当前 运行 线程循环回锁定请求时, Windows 将切换到互斥体的锁定请求队列中的第一个线程。我不知道这是否被记录在案,但我已经确认它是这样工作的。我不知道 Windows 中的 pthreads mutex 是否会以这种方式工作。
一种可能的替代方法是每个线程一个互斥锁,但我不知道这是否会导致任何问题,具体取决于 OS。每个线程使用一个信号量应该可以工作
旁注,如果使用条件变量,您可能会获得虚假唤醒,并且需要处理这种情况。
https://en.wikipedia.org/wiki/Spurious_wakeup
但是,如果使用本机 Windows 同步类型,如互斥量、信号量...,则不会发生虚假唤醒。
对于一些OS,比如Linux,这些问题已经足够了,一些高端多处理/多线程应用程序安装内核级驱动程序来实现内核时间自旋锁可以避免这些问题。
@rcgldr 已经对所涉及的时间进行了很好的解释。如果您想增加给另一个线程机会的几率,请尝试添加对 pthread_yield 的调用,这应该使调度程序有机会选择不同的线程,尽管这也不能保证。