我注意到当我有一个锁定和解锁线程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);
}
}
答案 0 :(得分:17)
而不是担心草叶,退后一步观察整个森林。
依赖于两个线程的任何算法都可能在彼此的脚趾上间接地踩踏,这本身就是低效的。尝试找到一种方法来大幅减少交互的需要。
例如,如果一个线程产生数据而另一个线程消耗它,那么人们很容易想到一个效率低下的算法,其中生产者在共享内存中发布数据然后等待另一个消费它。与此同时,消费者正在等待生产者完成等等等。生产者写入文件或管道以及消费者从中读取消息时,这一切都大大简化了。
答案 1 :(得分:13)
pthread_mutex_lock
和pthread_mutex_unlock
根据争用而有所不同:
但是,在大多数情况下和大多数实现中,互斥锁应该是最便宜的锁定原语。偶尔螺旋锁可能表现更好。我永远不会期望信号量表现更好。
答案 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;