I'm using pthread_mutex_t for locking.
我正在使用pthread_mutex_t进行锁定。
pthread_mutex_t m_lock;
void get1() {
cout<<"Start get 1"<<endl;
pthread_mutex_lock(&m_lock);
get2();
pthread_mutex_unlock(&m_lock);
cout<<"End get 1"<<endl;
}
void get2() {
cout<<"Start get 2"<<endl;
pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
pthread_mutex_unlock(&m_lock);
cout<<"End get 2"<<endl;
}
// The thread call to run function
void* run(void* p) {
get1();
}
Lets say I have only one thread that calls to run function, so: get1 lock the m_lock and call to get2, but when it tries to lock m_lock, it waits that the lock will be unlock (something that not happen) and we got a deadlock.
假设我只有一个调用run函数的线程,所以:get1锁定m_lock并调用get2,但是当它试图锁定m_lock时,它等待锁定将被解锁(不会发生的事情)我们得到了一个僵局。
My question is how can I avoid this case when the same thread that locked the lock in get1, will not need to wait for the lock in get2 (because it is the same thread)?
我的问题是,当在get1中锁定锁的同一个线程不需要等待get2中的锁(因为它是同一个线程)时,我怎么能避免这种情况?
For example, in Java this case never can happen when you use synchornized.
例如,在Java中,当您使用synchornized时,这种情况永远不会发生。
public Test implements Runnable {
public void get1() {
System.out.println("Start get 1");
synchronized (this) {
get2();
}
System.out.println("End get 1");
}
public void get2() {
System.out.println("Start get 2");
synchronized (this) {
}
System.out.println("End get 2");
}
@Override
public void run() {
get1();
}
}
No deadlock here.
这里没有僵局。
I want the same result in my C code please.
我想在我的C代码中得到相同的结果。
Thanks.
4 个解决方案
#1
11
As noted by Kami Kaze in the comments, if this is your full example, then it's a non-issue: there's only one path leading to get2
, and this path already acquires the mutex; simply omit acquiring it a second time.
正如Kami Kaze在评论中所指出的,如果这是你的完整示例,那么这是一个非问题:只有一条通向get2的路径,而且这条路径已经获得了互斥锁;只是省略第二次获取它。
However, in general, it's possible to think of scenarios where it's not that clear. In this case, you can make the mutex recursive/reentrant:
但是,一般来说,可以考虑不清楚的情况。在这种情况下,您可以使互斥锁递归/重入:
In computer science, the reentrant mutex (recursive mutex, recursive lock) is particular type of mutual exclusion (mutex) device that may be locked multiple times by the same process/thread, without causing a deadlock.
在计算机科学中,可重入互斥(递归互斥,递归锁定)是特殊类型的互斥(互斥)设备,可以被同一进程/线程多次锁定,而不会导致死锁。
In your settings, this would be via pthread_mutexattr_settype
:
在您的设置中,这将通过pthread_mutexattr_settype:
pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);
#2
0
With this:
pthread_mutex_lock(&m_lock);
get2();
pthread_mutex_unlock(&m_lock);
you have locked the entire get2()
. So, there's no point in taking the same lock again inside get2()
function. Just remove the locking and unlocking code from get2()
.
你已经锁定了整个get2()。因此,在get2()函数中再次使用相同的锁是没有意义的。只需从get2()中删除锁定和解锁代码即可。
If only the code part in get2()
requires a locking then get rid of the locking and unlocking from get1()
function.
如果只有get2()中的代码部分需要锁定,那么从get1()函数中解除锁定和解锁。
For example, in Java this case never can happen when you use synchornized.
例如,在Java中,当您使用synchornized时,这种情况永远不会发生。
In your code there the synchronized regions are not interlinked. So, for a similar comparison, you need to use a different mutex in get2()
function.
在您的代码中,同步区域不相互关联。因此,对于类似的比较,您需要在get2()函数中使用不同的互斥锁。
#3
0
This is called lock recursion.
这称为锁递归。
The last argument to pthread_mutex_init
is an attributes struct. You can set the attributes to allow recursive locking with pthread_mutexattr_settype(..., PTHREAD_MUTEX_RECURSIVE)
.
pthread_mutex_init的最后一个参数是属性struct。您可以设置属性以允许使用pthread_mutexattr_settype(...,PTHREAD_MUTEX_RECURSIVE)进行递归锁定。
But, I must add some editorial content here. I believe very strongly that lock recursion is almost always a bug. Or it will lead to impossible to debug bugs later in the programs life time.
但是,我必须在这里添加一些编辑内容。我非常强烈地相信锁定递归几乎总是一个错误。或者它将导致无法在程序生命周期的后期调试错误。
A locking operation can be reasoned to mean "when the lock function returns the object protected by the lock is in a known state and this state will not change until the unlock function is called". This means that if get1
has started to modify the object you protect with the lock and then get2
recurses that lock, this contract is broken twice. First because get2
succeeds obtaining the lock while the object is not in a known state, second because the object is modified while get1
thinks it owns the lock.
锁定操作可以被理解为“当锁定函数返回由锁定保护的对象处于已知状态并且该状态在调用解锁函数之前不会改变”。这意味着如果get1已经开始使用锁修改您保护的对象,然后get2递归该锁,则此合约将被中断两次。首先是因为get2在对象未处于已知状态时成功获取锁定,第二个因为该对象被修改而get1认为它拥有锁定。
Sure, we often get away with doing things like this but it is a terrible practice. Redesign your program to not recurse locks. The standard way to do this would be to implement a function called get2_locked
and get2
obtains the lock and calls get2_locked
while get1
already knows it has the lock and would call get2_locked
.
当然,我们经常逃避这样做,但这是一种可怕的做法。重新设计您的程序以不递归锁定。执行此操作的标准方法是实现一个名为get2_locked的函数,get2获取锁并调用get2_locked,而get1已经知道它具有锁并调用get2_locked。
#4
0
I assume that get1
really does more than just acquire the lock and call get2
? Otherwise what is the point of get1
?
我假设get1真的不只是获取锁并调用get2?否则get1有什么意义?
If that's the case you could solve it by having a get3
function which does the main part of get2
(the part you don't show here) and which doesn't lock. Then call that new function from get1
instead (and of course from get
too):
如果是这种情况你可以通过get3函数来解决它,这个函数执行get2的主要部分(这里没有显示的部分)并且没有锁定。然后从get1调用该新函数(当然也从get获取):
void get1()
{
// Do something here
cout<<"Start get 1"<<endl;
pthread_mutex_lock(&m_lock);
get3(); // <-- Note call get3 instead here
pthread_mutex_unlock(&m_lock);
cout<<"End get 1"<<endl;
// Do something more here
}
void get2()
{
cout<<"Start get 2"<<endl;
pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
get3(); // <-- Note call to get3 here
pthread_mutex_unlock(&m_lock);
cout<<"End get 2"<<endl;
}
void get3()
{
// Do the actual work of get2 here...
// Note: No locking here
}
#1
11
As noted by Kami Kaze in the comments, if this is your full example, then it's a non-issue: there's only one path leading to get2
, and this path already acquires the mutex; simply omit acquiring it a second time.
正如Kami Kaze在评论中所指出的,如果这是你的完整示例,那么这是一个非问题:只有一条通向get2的路径,而且这条路径已经获得了互斥锁;只是省略第二次获取它。
However, in general, it's possible to think of scenarios where it's not that clear. In this case, you can make the mutex recursive/reentrant:
但是,一般来说,可以考虑不清楚的情况。在这种情况下,您可以使互斥锁递归/重入:
In computer science, the reentrant mutex (recursive mutex, recursive lock) is particular type of mutual exclusion (mutex) device that may be locked multiple times by the same process/thread, without causing a deadlock.
在计算机科学中,可重入互斥(递归互斥,递归锁定)是特殊类型的互斥(互斥)设备,可以被同一进程/线程多次锁定,而不会导致死锁。
In your settings, this would be via pthread_mutexattr_settype
:
在您的设置中,这将通过pthread_mutexattr_settype:
pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);
#2
0
With this:
pthread_mutex_lock(&m_lock);
get2();
pthread_mutex_unlock(&m_lock);
you have locked the entire get2()
. So, there's no point in taking the same lock again inside get2()
function. Just remove the locking and unlocking code from get2()
.
你已经锁定了整个get2()。因此,在get2()函数中再次使用相同的锁是没有意义的。只需从get2()中删除锁定和解锁代码即可。
If only the code part in get2()
requires a locking then get rid of the locking and unlocking from get1()
function.
如果只有get2()中的代码部分需要锁定,那么从get1()函数中解除锁定和解锁。
For example, in Java this case never can happen when you use synchornized.
例如,在Java中,当您使用synchornized时,这种情况永远不会发生。
In your code there the synchronized regions are not interlinked. So, for a similar comparison, you need to use a different mutex in get2()
function.
在您的代码中,同步区域不相互关联。因此,对于类似的比较,您需要在get2()函数中使用不同的互斥锁。
#3
0
This is called lock recursion.
这称为锁递归。
The last argument to pthread_mutex_init
is an attributes struct. You can set the attributes to allow recursive locking with pthread_mutexattr_settype(..., PTHREAD_MUTEX_RECURSIVE)
.
pthread_mutex_init的最后一个参数是属性struct。您可以设置属性以允许使用pthread_mutexattr_settype(...,PTHREAD_MUTEX_RECURSIVE)进行递归锁定。
But, I must add some editorial content here. I believe very strongly that lock recursion is almost always a bug. Or it will lead to impossible to debug bugs later in the programs life time.
但是,我必须在这里添加一些编辑内容。我非常强烈地相信锁定递归几乎总是一个错误。或者它将导致无法在程序生命周期的后期调试错误。
A locking operation can be reasoned to mean "when the lock function returns the object protected by the lock is in a known state and this state will not change until the unlock function is called". This means that if get1
has started to modify the object you protect with the lock and then get2
recurses that lock, this contract is broken twice. First because get2
succeeds obtaining the lock while the object is not in a known state, second because the object is modified while get1
thinks it owns the lock.
锁定操作可以被理解为“当锁定函数返回由锁定保护的对象处于已知状态并且该状态在调用解锁函数之前不会改变”。这意味着如果get1已经开始使用锁修改您保护的对象,然后get2递归该锁,则此合约将被中断两次。首先是因为get2在对象未处于已知状态时成功获取锁定,第二个因为该对象被修改而get1认为它拥有锁定。
Sure, we often get away with doing things like this but it is a terrible practice. Redesign your program to not recurse locks. The standard way to do this would be to implement a function called get2_locked
and get2
obtains the lock and calls get2_locked
while get1
already knows it has the lock and would call get2_locked
.
当然,我们经常逃避这样做,但这是一种可怕的做法。重新设计您的程序以不递归锁定。执行此操作的标准方法是实现一个名为get2_locked的函数,get2获取锁并调用get2_locked,而get1已经知道它具有锁并调用get2_locked。
#4
0
I assume that get1
really does more than just acquire the lock and call get2
? Otherwise what is the point of get1
?
我假设get1真的不只是获取锁并调用get2?否则get1有什么意义?
If that's the case you could solve it by having a get3
function which does the main part of get2
(the part you don't show here) and which doesn't lock. Then call that new function from get1
instead (and of course from get
too):
如果是这种情况你可以通过get3函数来解决它,这个函数执行get2的主要部分(这里没有显示的部分)并且没有锁定。然后从get1调用该新函数(当然也从get获取):
void get1()
{
// Do something here
cout<<"Start get 1"<<endl;
pthread_mutex_lock(&m_lock);
get3(); // <-- Note call get3 instead here
pthread_mutex_unlock(&m_lock);
cout<<"End get 1"<<endl;
// Do something more here
}
void get2()
{
cout<<"Start get 2"<<endl;
pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
get3(); // <-- Note call to get3 here
pthread_mutex_unlock(&m_lock);
cout<<"End get 2"<<endl;
}
void get3()
{
// Do the actual work of get2 here...
// Note: No locking here
}