C语言 这是递归互斥锁的合适用例吗?



我从各种来源听说过 (1, 2) 应该避免使用递归互斥体,因为它可能是黑客或 糟糕的设计。然而,有时我认为它们可能是必要的。有鉴于此, 以下是否适合递归互斥锁?

// main.c
// gcc -Wall -Wextra -Wpedantic main.c -pthread
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif /* _GNU_SOURCE */
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct synchronized_counter
{
int count;
pthread_mutex_t mutex;
pthread_mutexattr_t mutexattr;
} synchronized_counter;
synchronized_counter* create_synchronized_counter()
{
synchronized_counter* sc_ptr = malloc(sizeof(synchronized_counter));
assert(sc_ptr != NULL);
sc_ptr->count = 0;
assert(pthread_mutexattr_init(&sc_ptr->mutexattr) == 0);
assert(pthread_mutexattr_settype(&sc_ptr->mutexattr, 
PTHREAD_MUTEX_RECURSIVE) == 0);
assert(pthread_mutex_init(&sc_ptr->mutex, &sc_ptr->mutexattr) == 0);
return sc_ptr;
}
void synchronized_increment(synchronized_counter* sc_ptr)
{
assert(pthread_mutex_lock(&sc_ptr->mutex) == 0);
sc_ptr->count++;
assert(pthread_mutex_unlock(&sc_ptr->mutex) == 0);
}
int main()
{
synchronized_counter* sc_ptr = create_synchronized_counter();
// I need to increment this counter three times in succesion without having
// another thread increment it in between. Therefore, I acquire a lock
// before beginning.
assert(pthread_mutex_lock(&sc_ptr->mutex) == 0);
synchronized_increment(sc_ptr);
synchronized_increment(sc_ptr);
synchronized_increment(sc_ptr);
assert(pthread_mutex_unlock(&sc_ptr->mutex) == 0);
return 0;
}

编辑

我想用一个简单的例子来问这个问题,但也许它太简单了。这就是我想象的更现实的场景:我有一个将由多个线程访问的堆栈数据结构。特别是,有时一个线程会从堆栈中弹出 n 个元素,但它必须一次全部完成(中间没有另一个线程从堆栈中推送或弹出(。设计问题的症结在于,我是否应该让客户端使用非递归互斥锁自己管理锁定堆栈,或者让堆栈提供同步的简单方法以及递归互斥锁,客户端可以使用这些方法进行多个也同步的原子事务。

您的两个示例(原始synchronized_counter和编辑中的堆栈(都是使用递归互斥锁的正确示例,但是如果您正在构建数据结构,它们将被视为糟糕的 API 设计。我会试着解释为什么。

  1. 公开内部- 调用方需要使用保护对数据结构成员的内部访问的相同锁。这为滥用该锁而不是访问数据结构提供了可能性。这可能会导致锁争用 - 或者更糟 - 死锁。

  2. 效率- 实施专门的批量操作(如increment_by(n)pop_many(n)(通常更有效。

    • 首先,它允许数据结构优化操作 - 也许计数器可以只做count += n或者堆栈可以在一次操作中从链表中删除n项。[1]
    • 其次,您不必为每个操作锁定/解锁互斥锁,从而节省时间。[2]

也许使用递归互斥锁的更好示例如下:

  • 我有一个包含两种方法的类FooBar.
  • 该类被设计为单线程。
  • 有时Foo打电话Bar.

我想使类线程安全,所以我向类添加一个互斥锁并将其锁定在FooBar中。现在我需要确保Bar在从Foo调用时可以锁定互斥锁。

在没有递归互斥锁的情况下解决此问题的一种方法是创建一个私有unsynchronized_bar,并在锁定互斥锁后同时FooBar调用它。

如果Foo是一个可以由子类实现并用于调用Bar的虚拟方法,或者如果Foo调用程序的其他部分可以回调Bar,这可能会变得棘手。但是,如果关键部分(受互斥锁保护的代码(中的代码调用其他任意代码,则程序的行为将难以理解,并且很容易导致不同线程之间的死锁,即使您使用递归互斥锁。

最好的建议是通过良好的设计而不是花哨的同步原语来解决并发问题。


[1] 有一些更棘手的模式,例如"弹出一个项目,查看它,决定是否弹出另一个项目",但这些模式可以通过向批量操作提供谓词来实现。

[2] 实际上,锁定您已经拥有的互斥锁应该非常便宜,但在您的示例中,它至少需要调用外部库函数,该函数不能内联。

您描述的逻辑实际上不是递归互斥锁,也不是一个合适的案例。

而且,如果您确实需要确保另一个线程不会增加计数器,我很抱歉地告诉您,您编写的逻辑无法确保这一点。

因此,我建议你退后一步,清醒头脑,重新考虑你的实际用例。 我认为对递归互斥体的困惑使您误入歧途。 很可能是这样,你现在的逻辑synchronized_increment......事实上,需要整个方法...是不必要的,你在main中显示的逻辑就是你真正需要的,毕竟它只是一个简单的变量。

最新更新