multithreading 如何正确销毁pthread互斥锁

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17169697/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 01:23:02  来源:igfitidea点击:

How to correctly destroy pthread mutex

multithreadinglockingpthreadsmutex

提问by Sridhar V

How exactly i can destroy a pthread mutex variable ?

我究竟如何才能销毁 pthread 互斥变量?

Here is what i want to do. I want to have objects (structure variables) cached , which are looked up by key. I want to have minimum granularity of locks here. So i want to have a lock for each object probably embedded in the structure so that i can have object level locking.

这是我想要做的。我想缓存对象(结构变量),通过键查找。我想在这里拥有最小的锁粒度。所以我想为每个可能嵌入在结构中的对象锁定一个锁,以便我可以拥有对象级锁定。

Now the problem is how to safely destroy these objects ? Looks like first step is to remove the object from the lookup table so that the object is not accessible in future that is fine.

现在的问题是如何安全地销毁这些对象?看起来第一步是从查找表中删除对象,以便将来无法访问该对象,这很好。

I want to free the object from the cache. Now how to destroy/free mutex correctly ? pthread_mutex_destroy document says we should not use the pthread_mutex_destroy while the mutex is locked. Lets say a thread decides to destroy the object it needs to destroy the lock so it releases the lock and does a pthread_mutex_destroy. What happens to the other threads waiting for the objects lock ?

我想从缓存中释放对象。现在如何正确销毁/释放互斥锁?pthread_mutex_destroy 文档说我们不应该在互斥锁被锁定时使用 pthread_mutex_destroy。假设一个线程决定销毁它需要销毁锁的对象,因此它释放锁并执行 pthread_mutex_destroy。等待对象锁定的其他线程会发生什么?

Here is the code to simulate the above , note i used sleep(2) to magnify the effect of race .

下面是模拟上面的代码,注意我使用 sleep(2) 来放大 Race 的效果。



#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

typedef struct exampleObj {
   pthread_mutex_t mutex;
   int key;
   int value1;
   int value2;
}exampleObj;

exampleObj sharedObj = {PTHREAD_MUTEX_INITIALIZER,0,0,0};

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

exampleObj* Lookup(int key) {
   return &sharedObj;
}

void* thrFunc(void* id) {
   int i = (*((int*)id));
   char errBuf[1024];
   exampleObj * obj = Lookup(0);

   if (pthread_mutex_lock(&obj->mutex)) {
      printf("Locking failed %d \n",i);
      return NULL;
   }
   // Do something
   printf("My id %d will do some work for 2 seconds.\n",i);
   sleep(2);
   pthread_mutex_unlock(&obj->mutex);
   int errNum = pthread_mutex_destroy(&obj->mutex);
   strerror_r(errNum,errBuf,1024);
   printf("Destroying mutex from thread %d : %s\n ",errNum,errBuf);
   return NULL;
}

int main() {
   pthread_t thrds[10];
   int i;
   int args[10];
   char errBuf[1024];
   int errNum = 1;

   for (i=0;i<10;i++){
      args[i] = i;
      pthread_create(&thrds[i],NULL,thrFunc,args+i);
   }

   for (i=0;i<10;i++){
      pthread_join(thrds[i],NULL);
   }
   return 0;
}

Multiple threads succeeds in destroying the mutex. And the remaining threads hang for ever. Gdb shows those threads are waiting for the lock.

多个线程成功破坏了互斥锁。剩下的线程永远挂起。Gdb 显示那些线程正在等待锁定。

回答by caf

The basic problem you have is that removing an object from the cache is something that requires synchronisation at the cache level, not the object level.

您遇到的基本问题是从缓存中删除对象需要在缓存级别而不是对象级别进行同步。

One way to implement this is by having a global lock for the entire cache that is only held during lookups, and is dropped once the object lock has been acquired. This lock can be a reader-writer lock, held for writing only if a thread is going to remove the object. So a thread that wishes to usea cache object would do:

实现这一点的一种方法是为整个缓存设置一个全局锁,该锁仅在查找期间保留,并在获取对象锁后删除。这个锁可以是一个读写锁,只有在线程要删除对象时才会被持有。因此,希望使用缓存对象的线程将执行以下操作:

pthread_rwlock_rdlock(&cache_lock);
exampleObj * obj = Lookup(key);
pthread_mutex_lock(&obj->mutex);
pthread_rwlock_unlock(&cache_lock);

/* Do some work on obj */

pthread_mutex_unlock(&obj->mutex);

and a thread that wishes to destroy a cache object would do:

并且希望销毁缓存对象的线程将执行以下操作:

pthread_rwlock_wrlock(&cache_lock);
exampleObj * obj = Lookup(key);
pthread_mutex_lock(&obj->mutex);
Remove(key);
pthread_rwlock_unlock(&cache_lock);

/* Do some cleanup work on obj */
pthread_mutex_unlock(&obj->mutex);
pthread_mutex_destroy(&obj->mutex);

(where the Remove()function removes the function from the cache so that subsequent Lookup()functions cannot return it).

Remove()该函数从缓存中删除该函数,以便后续Lookup()函数无法返回它)。

回答by sheyll

I want to free the object from the cache. Now how to destroy/free mutex correctly ? pthread_mutex_destroy document says we should not use the pthread_mutex_destroy while the mutex is locked. Lets say a thread decides to destroy the object it needs to destroy the lock so it releases the lock and does a pthread_mutex_destroy. What happens to the other threads waiting for the objects lock ?

我想从缓存中释放对象。现在如何正确销毁/释放互斥锁?pthread_mutex_destroy 文档说我们不应该在互斥锁被锁定时使用 pthread_mutex_destroy。假设一个线程决定销毁它需要销毁锁的对象,因此它释放锁并执行 pthread_mutex_destroy。等待对象锁定的其他线程会发生什么?

Well I hope I get your intention right, I had the exact same problem. Anyway I realized, later that I was stupid: Complaining about undefined behaviour of pthread_mutex_*functions afterpthread_mutex_destroy()is like complaining about SEGFAULTSwhen accessing a pointer after free().

好吧,我希望我的意图是正确的,我遇到了完全相同的问题。反正我认识,后来我是愚蠢的:抱怨的未定义行为pthread_mutex_*功能pthread_mutex_destroy()就像是抱怨SEGFAULTS后访问的指针时free()

Most C programs are modelled around the paradigm that every program must make sure that memory is not accessed after some sort of destruction. Good C programs will have a design that prevents pointers from being spread everywhere, so that destruction happens only at well defined places, when no other variable contains a pointer anymore. This is not at all a concern in garbage collected languages.

大多数 C 程序都围绕这样一种范式建模,即每个程序都必须确保在某种破坏后不会访问内存。好的 C 程序会设计出一种防止指针散布到任何地方的设计,这样破坏只会发生在定义明确的地方,当其他变量不再包含指针时。这在垃圾收集语言中根本不是问题。

Solution 1: Use refcounting like it is done for memory allocation. The refcounter is accessed via atomic functions. (Use the glib, it contains great, portable stuff)

解决方案 1:像内存分配一样使用引用计数。引用计数器通过原子函数访问。(使用 glib,它包含很棒的、可移植的东西)

Solution 1b: Use refcounting like it is done for memory allocation, sperate the kinds of workers that are important from those that aren't and use weak references in the later so that they do not prevent object destruction.

解决方案 1b:像内存分配一样使用引用计数,将重要的工作人员与不重要的工作人员分开,并在后面使用弱引用,这样它们就不会阻止对象破坏。

Solution 2: Do not destroy the mutex. Why bother with saving RAM? Just make a global static array of like 128k objects. Add a struct member wich indicates the state of the object. Instead of destruction just atomic compare and set the state variable, and print an error in the threads that access an object in "DISABLED" state.

解决方案2:不要破坏互斥锁。为什么要费心节省内存?只需创建一个包含 128k 个对象的全局静态数组即可。添加指示对象状态的结构成员。而不是破坏只是原子比较并设置状态变量,并在访问处于“禁用”状态的对象的线程中打印错误。

Solution 3 - The hard way: Don't do shared memory concurrency. Combine a thread pool which matches the number of CPUs on the system, use non-blocking IO, message objects and state-machine design. Make message queues for each task, and let tasks communicate only by messages enqueued in the queue of the other. Put the queue in the same 'select' or 'pollfd' set that contains the sockets/filedescriptors. To shuffle big data (3d game) between state machines, use a struct with an atomic refcounter and copy on write semantics. This will in most cases be the most performant, stable and maintainable solution.

解决方案 3 - 困难的方法:不要进行共享内存并发。结合系统上CPU数量匹配的线程池,使用非阻塞IO、消息对象和状态机设计。为每个任务建立消息队列,让任务只通过在另一个队列中排队的消息进行通信。将队列放在包含套接字/文件描述符的同一个“select”或“pollfd”集中。要在状态机之间打乱大数据(3d 游戏),请使用带有原子引用计数器的结构体并复制写入语义。在大多数情况下,这将是最高效、最稳定和可维护的解决方案。

If what you do has anything to do with performance, think twice about using the atomic operations. They can be more expensive than mutexes.

如果您所做的与性能有关,请在使用原子操作时三思而后行。它们可能比互斥锁更昂贵。

回答by NES

I couldn't agree more with caf on this. We have done something similar in certain implementation (e.g. refer ifData_createReference & ifData_removeReference routines in ifMIB.c). The basic idea is keeping a global lock to guard the entire object list and an object level lock for guarding individual entry in the list.

在这点上,我完全同意 caf 的观点。我们在某些实现中做了类似的事情(例如,在ifMIB.c 中参考 ifData_createReference 和 ifData_removeReference 例程)。基本思想是保持一个全局锁来保护整个对象列表和一个对象级锁来保护列表中的单个条目。

When we have to create a new entry in the list, take WRITE lock on the list and add a new entry, so that entry is added consistently to all the users of the list. And release the list lock.

当我们必须在列表中创建一个新条目时,对列表进行 WRITE 锁定并添加一个新条目,以便将该条目一致地添加到列表中的所有用户。并释放列表锁。

When we have to look-up/access an entry from the list, take a READ lock on the list and search for the entry. Once we find the entry, take object lock in READ mode for read-only operations / take object lock in WRITE mode for modifying the object entry. Now, release the list lock. Now once we are done with processing of the object entry release the object lock as well.

当我们必须从列表中查找/访问一个条目时,在列表上获取一个 READ 锁并搜索该条目。一旦我们找到条目,在 READ 模式下为只读操作获取对象锁定/在 WRITE 模式下获取对象锁定以修改对象条目。现在,释放列表锁。现在一旦我们完成了对象条目的处理,也释放对象锁。

When the object entry has to be removed from the list, take a WRITE lock on the list. Search and find the object entry in the list. Take a WRITE lock on the object entry, this will ensure that you are the ONLY current user for the object. Now remove the entry from the listing, as no one can search it any more in the list. And release the object lock immediately. Then, release the list lock. Now destroy the object and release the object resources.

当对象条目必须从列表中删除时,对列表进行 WRITE 锁定。在列表中搜索并找到对象条目。对对象条目进行 WRITE 锁定,这将确保您是该对象的唯一当前用户。现在从列表中删除该条目,因为没有人可以再在列表中搜索它。并立即释放对象锁。然后,释放列表锁。现在销毁对象并释放对象资源。

回答by Casey

It's undefined behavior to (a) attempt to destroy a locked mutex, or (b) reference a destroyed mutex other than to call pthread_mutex_initto recreate it (See documentation). That means that the thread that destroys your shared mutex is going to race with the others locking it, and either (1) destroy happens first, other threads invoke undefined behavior trying to lock because of (b) or (2) lock in another thread happens first and destroying thread invokes undefined behavior because of (a).

(a) 尝试销毁锁定的互斥锁,或 (b) 引用已销毁的互斥锁而不是调用pthread_mutex_init重新创建它是未定义的行为(请参阅文档)。这意味着销毁共享互斥锁的线程将与其他锁定它的线程竞争,并且 (1) 销毁首先发生,其他线程调用未定义的行为试图锁定,因为 (b) 或 (2) 锁定另一个线程由于(a),首先发生并销毁线程调用未定义的行为。

You need to change your design so that a mutex under active contention is never destroyed. For your example, you could destroy the shared mutex in mainafter all threads are joined. For the program you describe, you probably need to insert a reference count in the objects.

您需要更改您的设计,以便永远不会破坏处于活动状态的互斥锁。对于您的示例,您可以在main加入所有线程后销毁共享互斥锁。对于您描述的程序,您可能需要在对象中插入一个引用计数。