Linux中锁的总结

目录

1 前言

2 注意事项

2.1 明确锁的范围

2.2 减少锁的粒度

3 避免死锁的建议

1 前言

        实际开发过程中,使用锁会带来一定性能的损失,但如果使用锁也能满足性能要求,对于锁的使用就无妨。使用锁可能带来如下性能损失:

  • 加锁和解锁操作,本身有一定的开销;
  • 临界区的代码不能并发执行;
  • 进入临界区的次数过于频繁,线程之间对临界区的争夺太过激烈,若线程竞争互斥量失败,就会陷入阻塞,让出 CPU,导致执行上下文切换的次数要远远多于不使用互斥量的版本。

替代锁的方式有很多,比如无锁队列

2 注意事项

2.1 明确锁的范围

举例:

if(hashtable.is_empty())
{
    pthread_mutex_lock(&mutex);
    htable_insert(hashtable, &elem);
    pthread_mutex_unlock(&mutex);
}

   可以发现上述代码,虽然对hashtable的插入做了锁的保护,但是判断 hash_table 是否为空也需要使用锁保护,所以正确的写法应该是:

pthread_mutex_lock(&mutex);
if(hashtable.is_empty())
{   
    htable_insert(hashtable, &elem);  
}
pthread_mutex_unlock(&mutex);

2.2 减少锁的粒度

      通过减少被锁的代码范围,减少被锁的时间粒度,从而提高执行效率。

示例1:

void TaskPool::addTask(Task* task)
{
    std::lock_guard<std::mutex> guard(m_mutexList); 
    std::shared_ptr<Task> spTask;
    spTask.reset(task);            
    m_taskList.push_back(spTask);
          
    m_cv.notify_one();
}

       上述代码中 guard 锁保护 m_taskList,仔细分析下这段代码发现,只需要锁住m_taskList的插入处理动作就行,其他的处理并不需要,可修改如下:

void TaskPool::addTask(Task* task)
{
    std::shared_ptr<Task> spTask;
    spTask.reset(task);

    {
        std::lock_guard<std::mutex> guard(m_mutexList);             
        m_taskList.push_back(spTask);
    }
    
    m_cv.notify_one();
}

示例2:

void EventLoop::doPendingFunctors()
{
    std::unique_lock<std::mutex> lock(mutex_);
	for (size_t i = 0; i < pendingFunctors_.size(); ++i)
	{
		pendingFunctors_[i]();
	}
}

       上述代码中 pendingFunctors_ 是被锁保护的对象,需要执行完所有的对象才会释放锁,这严重的降低了执行效率。可修改代码如下:

void EventLoop::doPendingFunctors()
{
	std::vector<Functor> functors;
	
	{
		std::unique_lock<std::mutex> lock(mutex_);
		functors.swap(pendingFunctors_);
	}

	for (size_t i = 0; i < functors.size(); ++i)
	{
		functors[i]();
	}	
}

       修改之后的代码使用了一个局部变量 functors,然后把 pendingFunctors_ 中的内容倒换到 functors 中,这样就可以释放锁了,允许其他线程操作 pendingFunctors_ ,现在只要继续操作本地对象 functors 就可以了,提高了效率。

3 避免死锁的建议

  • 加了锁,一定记得释放锁。但可能会因逻辑疏忽忘记释放锁,所以强烈建议使用RAII技术封装锁。
  • 多线程请求锁的方向要一致,以避免死锁

关于“活锁”的理解:当多个线程使用 trylock 系列的函数时,由于多个线程相互谦让,导致即使在某段时间内锁资源是可用的,也可能导致需要锁的线程拿不到锁。所以尽量避免不要过多的线程使用 trylock 请求锁,以免出现“活锁”现象,这是对资源的一种浪费。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章