8

我究竟如何销毁 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

4 回答 4

4

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

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

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()函数无法返回它)。

于 2013-06-20T03:04:15.097 回答
1

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

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

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

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

于 2014-08-13T00:20:59.057 回答
0

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

好吧,我希望我能理解您的意图,我遇到了完全相同的问题。无论如何,后来我意识到我很愚蠢:抱怨pthread_mutex_*函数的未定义行为就像抱怨访问指针 after 时一样pthread_mutex_destroy()SEGFAULTSfree()

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

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

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

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

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

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

于 2013-11-07T20:07:24.260 回答
0

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

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

于 2013-06-18T14:58:37.843 回答