POSIX Thread API是否提供一种方法来测试调用线程是否已持有锁?

时间:2019-04-08 14:27:50

标签: pthreads

简短的问题:POSIX线程API是否为我提供一种确定调用线程是否已持有特定锁的方法?

长问题:

假设我想用锁保护数据结构。获取和释放锁需要在不同的功能中发生。涉及的函数之间的调用相当复杂(我正在将多线程添加到已有16年历史的代码库中)。例如:

  • do_dangerous_stuff()执行当前线程需要为其保持写锁定的操作。因此,由于调用者不一定持有该锁,所以我在函数的开头获取了该锁,并在该函数的末尾释放了该锁。
  • 另一个功能do_extra_dangerous_stuff()调用do_dangerous_stuff()。但是,在调用之前,它已经执行了还需要写锁定的操作,并且直到对do_dangerous_stuff()的调用返回之前,数据才是不一致的(因此释放并立即重新获取该锁定可能会破坏事情)。

实际上,这要复杂得多。可能有一堆调用do_dangerous_stuff()的函数,要求每个函数在调用do_dangerous_stuff()之前获取锁可能是不切实际的。有时,锁是在一个功能中获取的,而在另一功能中释放。

使用读取锁,只要确保释放与获取的锁数量相同的锁,我就可以从同一线程多次获取它。对于写锁,这不是一个选项(尝试这样做将导致死锁)。一个简单的解决方案是:测试当前线程是否已经持有该锁,如果不是,则获取该锁,反之,测试当前线程是否仍然持有该锁,如果存在则释放它。但是,这需要我测试当前线程是否已持有该锁-有没有办法做到这一点?

2 个答案:

答案 0 :(得分:1)

看着pthread_rwlock_wrlock()的{​​{3}},我看到它说:

  

如果成功,pthread_rwlock_wrlock()函数应返回零;否则,将返回错误编号以指示错误。

     

[…]

     

在以下情况下,pthread_rwlock_wrlock()函数可能会失败:

     

EDEADLK 当前线程已经拥有用于写入或读取的读写锁。

在我读到它时,EDEADLK从未用来表示涉及多个线程的链条,这些线程正在等待彼此的资源(从我的观察来看,这种死锁的确似乎导致了冻结,而不是EDEADLK) 。似乎专门表明该线程正在请求当前线程已经拥有的资源,这是我要测试的条件。

如果我对文档有误解,请告诉我。否则,解决方案是简单地致电pthread_rwlock_wrlock()。应该发生以下情况之一:

  • 它阻塞,因为另一个线程拥有该资源。当我们再次运行时,我们将保持锁定状态。照常营业。
  • 它返回零(成功),因为我们刚刚获得了锁(之前没有持有过)。照常营业。
  • 它返回EDEADLK,因为我们已经持有了锁。无需重新获取,但我们在释放锁时可能要考虑这一点,这取决于所讨论的代码,请参见下文
  • 它返回其他错误,表明确实有错误。与其他所有锁定操作相同。

跟踪我们获得锁并获得EDEADLK的次数可能是有意义的。从吉尔·汉密尔顿(Gil Hamilton)的答案中,锁定深度将对我们有用:

  • 获取锁后,将锁深度重置为0。
  • 每次获得EDEADLK时,将锁定深度增加1。
  • 使用以下各项来匹配每次获取锁的尝试:如果锁深度为0,则释放锁。否则会减小锁定深度。

这应该是线程安全的,无需进一步同步,因为锁定深度受到它所引用的锁定的有效保护(我们仅在按住锁定时触摸它)。

注意::如果当前线程已持有读取锁(其他人没有),这也将其报告为“已锁定”。需要进一步测试以确定当前持有的锁是否确实是写锁。如果有多个线程(其中有当前线程)持有读取锁,我不知道尝试获取写锁是否会返回EDEADLK或冻结该线程。这部分需要更多的工作...

答案 1 :(得分:0)

AFAIK,没有简单的方法可以完成您想做的事情。

在linux中,您可以使用“递归”互斥属性来实现您的目的(例如,此处显示为https://stackoverflow.com/a/7963765/1076479),但这不是“ posix”可移植的。

唯一真正可移植的解决方案是推出自己的同类产品。为此,您可以创建一个包含锁以及自己的线程索引(或等效线程)和所有权/递归计数的数据结构。

  

注意:伪代码掉在我头上

递归锁:

List<RadioButton> radioButtons = new ArrayList<RadioButton>();
    radioButtons.add( (RadioButton)findViewById(R.id.Pradio1) );
    radioButtons.add( (RadioButton)findViewById(R.id.Pradio2) );
    radioButtons.add( (RadioButton)findViewById(R.id.Pradio3) );

    for (RadioButton button : radioButtons){

        button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) processRadioButtonClick(buttonView);
            }
        });
    }

private void processRadioButtonClick(CompoundButton buttonView){

    for (RadioButton button : radioButtons){

        if (button != buttonView ) button.setChecked(false);
    }

}

在出路时,它更简单,因为您知道自己拥有该锁,因此只需确定是否应该释放它(即最后一次解锁)。递归解锁:

// First try to acquire the lock without blocking...
if ((err = pthread_mutex_trylock(&mystruct.mutex)) == 0) {
    // Acquire succeeded. I now own the lock. 
    // (I either just acquired it or already owned it)
    assert(mystruct.owner == my_thread_index || mystruct.lock_depth == 0);
    mystruct.owner = my_thread_index;
    ++mystruct.lock_depth;
} else if (mystruct.owner == my_thread_index) {
    assert(err == EBUSY);
    // I already owned the lock. Now one level deeper
    ++mystruct.lock_depth;
} else {
    // I don't own the lock: block waiting for it.
    pthread_mutex_lock(&mystruct.mutex);
    assert(mystruct.lock_depth == 0);
}

我也想在信任之前添加一些其他检查和断言以及重要的测试。