【问题标题】:Does the POSIX Thread API offer a way to test if the calling thread alreeady holds a lock?POSIX 线程 API 是否提供了一种测试调用线程是否已经持有锁的方法?
【发布时间】:2019-04-08 14:27:50
【问题描述】:

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

长问题:

假设我想用锁保护一个数据结构。获取和释放锁需要发生在不同的函数中。所涉及的函数之间的调用相当复杂(我正在向一个 16 岁的代码库添加多线程)。例如:

  • do_dangerous_stuff() 执行当前线程需要持有写锁的事情。因此,我在函数开始时获取锁并在结束时释放它,因为调用者不一定持有锁。
  • 另一个函数do_extra_dangerous_stuff() 调用do_dangerous_stuff()。但是,在调用之前它已经做了一些需要写锁的事情,并且在对do_dangerous_stuff() 的调用返回之前数据是不一致的(因此释放并立即重新获取锁可能会破坏事情)。

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

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

【问题讨论】:

    标签: pthreads


    【解决方案1】:

    查看man pagepthread_rwlock_wrlock(),我看到它说:

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

    […]

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

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

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

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

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

    跟踪我们获得锁并获得EDEADLK 的次数可能是有意义的。从 Gil Hamilton 的回答中可以看出,锁定深度对我们有用:

    • 当我们获得锁时,将锁深度重置为 0。
    • 每次获得EDEADLK时,将锁定深度增加1。
    • 将每次获取锁的尝试与以下内容相匹配:如果锁深度为 0,则释放锁。否则减少锁定深度。

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

    警告:如果当前线程已经持有读锁(而没有其他线程持有),这也会报告它“已经被锁定”。需要进一步的测试来确定当前持有的锁是否确实是写锁。如果多个线程,包括当前线程,持有读锁,我不知道尝试获取写锁是否会返回EDEADLK 或冻结线程。这部分需要更多的工作......

    【讨论】:

    • 对我有意义
    【解决方案2】:

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

    在 linux 中,您可以使用“recursive”互斥体属性来实现您的目的(如此处所示:https://stackoverflow.com/a/7963765/1076479),但这不是“posix”-portable。

    唯一真正可移植的解决方案是推出您自己的等价物。您可以通过创建一个包含锁以及您自己的线程索引(或等效项)和所有权/递归计数的数据结构来做到这一点。

    CAVEAT:我脑子里冒出的伪代码

    递归锁:

    // 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);
    }
    

    在退出的过程中,它更简单,因为你知道你拥有锁,你只需要确定是否是时候释放它(即最后一次解锁)。递归解锁:

    if (--mystruct.lock_depth == 0) {
        assert(mystruct.owner == my_thread_index);
        // Last level of recursion unwound
        mystruct.owner = -1;    // Mark it un-owned
        pthread_mutex_unlock(&mystruct.mutex);
    }
    

    在信任它之前,我还想添加一些额外的检查和断言以及重要的测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多