如何正确销毁pthread互斥锁

时间:2013-06-18 13:08:04

标签: multithreading locking pthreads mutex

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

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

现在的问题是如何安全地销毁这些物体? 看起来第一步是从查找表中删除对象,使对象不是 将来可以使用,这很好。

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

这是模拟上面的代码,注意我用sleep(2)来放大效果 比赛。


#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;
}

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

4 个答案:

答案 0 :(得分:3)

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

实现此目的的一种方法是对整个缓存进行全局锁定,该缓存仅在查找期间保留,并在获取对象锁定后删除。此锁可以是读写器锁,只有在线程要删除对象时才会保留以进行写入。因此,希望使用缓存对象的线程可以:

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

并且希望销毁缓存对象的线程可以:

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

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

答案 1 :(得分:0)

未定义的行为是(a)尝试销毁锁定的互斥锁,或者(b)引用被破坏的互斥锁,而不是调用pthread_mutex_init来重新创建它(参见documentation)。这意味着破坏你的共享互斥锁的线程将与其他锁定它的竞争,并且(1)destroy先发生,其他线程调用未定义的行为试图锁定因为(b)或(2)锁定另一个线程首先发生并且因为(a)而破坏线程调用未定义的行为。

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

答案 2 :(得分:0)

  

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

嗯,我希望我的意图是正确的,我有完全相同的问题。无论如何,我意识到,后来我是愚蠢的:在<{em> pthread_mutex_*之后抱怨pthread_mutex_destroy()函数的未定义行为就像在{SEGFAULTS之后访问指针时抱怨free() 1}}。

大多数C程序都是围绕这个范例建模的,每个程序必须确保在某种破坏后不能访问内存。好的C程序将有一个设计,可以防止指针在任何地方传播,因此当没有其他变量包含指针时,只有在明确定义的地方才会发生破坏。这完全不是垃圾收集语言的问题。

解决方案1:使用引用计数来完成内存分配。通过原子函数访问refcounter。 (使用glib,它包含很棒的便携式东西)

解决方案1b:使用像内存分配一样的引用计数,对那些不重要的工作者进行处理,并在后面使用弱引用,这样它们就不会阻止对象的破坏。

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

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

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

答案 3 :(得分:0)

我对此与咖啡馆达成了不同意见。我们在某些实现中做了类似的事情(例如,参考ifMIB.c中的ifData_createReference&amp; ifData_removeReference例程)。基本思想是保持全局锁定以保护整个对象列表,并保持对象级锁定以保护列表中的单个条目。

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

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

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