pthread_mutex_lock / unlock的性能

时间:2011-06-23 20:59:41

标签: c++ c pthreads mutex

我注意到当我有一个锁定和解锁线程ALOT的算法时,我的性能大打出手。

有没有办法帮助这个开销?使用信号量会更多/更低效吗?

由于

typedef struct _treenode{
   struct _treenode *leftNode;
   struct _treenode *rightNode;
   int32_t data;
   pthread_mutex_t mutex;
}TreeNode;

pthread_mutex_t _initMutex = PTHREAD_MUTEX_INITIALIZER;

int32_t insertNode(TreeNode **_trunk, int32_t data){
   TreeNode **current;
   pthread_mutex_t *parentMutex = NULL, *currentMutex = &_initMutex;

   if(_trunk != NULL){
      current = _trunk;
      while(*current != NULL){
         pthread_mutex_lock(&(*current)->mutex);
         currentMutex = &(*current)->mutex;
         if((*current)->data < data){
            if(parentMutex != NULL)
               pthread_mutex_unlock(parentMutex);
            pthreadMutex = currentMutex;
            current = &(*current)->rightNode;
         }else if((*current)->data > data){
            if(parentMutex != NULL)
               pthread_mutex_unlock(parentMutex);
            parentMutex = currentMutex;
            current = &(*current)->leftNode;
         }else{
            pthread_mutex_unlock(currentMutex);
            if(parentMutex != NULL)
               pthread_mutex_unlock(parentMutex);
            return 0;
         }
      }
      *current = malloc(sizeof(TreeNode));
      pthread_mutex_init(&(*current)->mutex, NULL);
      pthread_mutex_lock(&(*current)->mutex);
      (*current)->leftNode = NULL;
      (*current)->rightNode = NULL;
      (*current)->data = data;
      pthread_mutex_unlock(&(*current)->mutex);
      pthread_mutex_unlock(currentMutex);
   }else{
      return 1;
   }
   return 0;
}

int main(){
   int i;
   TreeNode *trunk = NULL;
   for(i=0; i<1000000; i++){
      insertNode(&trunk, rand() % 50000);
   }
}

6 个答案:

答案 0 :(得分:17)

而不是担心草叶,退后一步观察整个森林。

依赖于两个线程的任何算法都可能在彼此的脚趾上间接地踩踏,这本身就是低效的。尝试找到一种方法来大幅减少交互的需要。

例如,如果一个线程产生数据而另一个线程消耗它,那么人们很容易想到一个效率低下的算法,其中生产者在共享内存中发布数据然后等待另一个消费它。与此同时,消费者正在等待生产者完成等等等。生产者写入文件或管道以及消费者从中读取消息时,这一切都大大简化了。

答案 1 :(得分:13)

pthread_mutex_lockpthread_mutex_unlock根据争用而有所不同:

  1. 单线程使用 - 只存在一个线程,或者只有一个线程正在使用互斥锁及其保护的资源:锁定几乎是免费的,最多可能是80-100个周期。
  2. 使用资源的多个线程,但是锁定的间隔非常短,争用很少:锁定有一些成本,而且很难测量;成本主要包括使其他核心'/ cpus'缓存行无效。
  3. 显着的锁定争用:几乎每个锁定和解锁操作都需要内核的帮助,每次锁定/解锁的成本很容易达到几千(甚至几万)个周期。
  4. 但是,在大多数情况下和大多数实现中,互斥锁应该是最便宜的锁定原语。偶尔螺旋锁可能表现更好。我永远不会期望信号量表现更好。

答案 2 :(得分:8)

据我所知,你的锁定策略并不是最优的,因为大多数锁定都不会用来改变数据,只是为了读取并找到通过树的方式。

pthread_rwlock_t 可以提供帮助。您只需在树中的路径上读取锁定,直到您到达要进行某些修改的节点。然后你会在那里进行写锁定。通过这种方式,您可以让其他线程在不同分支中沿着树向下走时执行相同的任务。

只要没有与作者争用,pthread_rwlock_t的一个体面的实现就可以通过原子操作改变读者的计数器。这应该非常快。一旦存在争用,我认为它会像互斥锁一样昂贵。

答案 3 :(得分:1)

你的锁可能太精细了。当然,最佳粒度可能会因工作量而异。

您可以对整个树使用单个锁,可能表现更好。但是,如果你进行了大量的阅读并且插入/删除的次数相对较少,那么你最终会无缘无故地锁定整棵树。您可能希望使用读写器锁定,这将允许多个读者同时使用。

当您对链接列表的细粒度锁定和粗粒度锁定进行比较时,您的问题提醒我this other one。在粗粒度版本中,每个线程依次运行(不是并行),并且总运行时间略大于每个线程运行时间的总和,并且在细粒度版本中总运行时间远小于每个线程运行时间的总和,细粒度锁定的额外开销完全抵消了这些好处,使得细粒度版本比粗粒度版本慢。

答案 4 :(得分:0)

在pthread_mutex_lock / unlock的情况下,锁定和解锁是非常昂贵的操作。关于算法的更多细节,我可以提出一些建议,但据我所知,我无法肯定地告诉你任何事情。信号量是另一种选择(同样取决于算法),并且障碍是另一种有用的并发方法。为了帮助开销,您可以执行诸如使锁定更小粒度或更大粒度之类的操作。锁定多次迭代的循环是一个坏主意,您可能希望将它们移出循环。这只是一个例子,但我可以想出更多。它是关于确定锁的成本是否大于代码的关键部分的成本。如果您提供算法或一些示例代码,我很乐意看一下它。

答案 5 :(得分:-2)

pthread_mutex_lock和pthread_cond_wait是OS原语-它们将调用线程置于睡眠状态,将控制权转移到另一个线程。即它们涉及系统调用和大量开销。在两个线程之间的紧密集成中,您实际上并不想放弃任何控制甚至一个周期。

鉴于我建议使用volatile int变量而不是互斥锁:

volatile int data_ready = 0;
/*  ... */
while (!data_ready);
process_data();
data_ready = 0;