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 請求鎖,以免出現“活鎖”現象,這是對資源的一種浪費。

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