linux自旋鎖(spinlock)是一種用于保護共享資源的鎖機制,主要應用于多核處理器環境中。當一個核或線程嘗試獲取鎖時,如果發現鎖已被其他核持有,它會持續忙等(不斷循環檢查),而不是讓出cpu時間片。
自旋鎖的特點是適用于鎖的持有時間極短的場景,因為它在等待期間不會主動放棄CPU,而是不斷嘗試獲取鎖,這在多核系統中可以避免由于線程調度帶來的上下文切換開銷。
工作原理:
加鎖:線程嘗試獲取鎖,如果成功,則進入臨界區。如果鎖已被占用,線程會不停地輪詢檢查鎖是否釋放。
忙等(自旋):如果鎖被占用,線程會持續忙等,不會主動讓出CPU。這樣避免了上下文切換,但消耗了CPU資源,因此自旋鎖適用于鎖定時間較短的場景。
解鎖:當臨界區的任務完成后,線程釋放鎖,其他正在忙等的線程可以繼續嘗試獲取鎖。
自旋鎖常用于以下情況:
- 需要保護的代碼段執行時間非常短,能夠迅速釋放鎖。
- 不希望線程進入睡眠狀態或導致上下文切換,尤其是在內核中的中斷處理程序或者性能要求高的系統。
- 多核系統中,并發訪問的共享資源保護,避免線程在上下文切換中浪費時間。
自旋鎖與互斥鎖的比較如下:
實現方式上的區別:
自旋鎖是一種輕量級鎖機制,它在忙等狀態下獲取鎖。當線程無法獲取鎖時,不會進入睡眠或等待狀態,而是會不斷檢查鎖的狀態,直到可以成功獲取鎖。互斥鎖則是一種更高層的鎖,通常在無法獲取鎖時會導致線程進入阻塞狀態。互斥鎖可以讓操作系統將當前線程掛起,等待鎖可用時再喚醒。
開銷上的區別:
自旋鎖的主要優勢在于沒有上下文切換的開銷,特別適用于鎖持有時間很短的場景。由于自旋鎖不會導致線程休眠,所以在處理器繁忙時可能會浪費CPU時間。如果等待時間較長,忙等會消耗過多的CPU資源,反而導致效率下降。互斥鎖的開銷較大,因為線程在獲取不到鎖時會陷入休眠,直到獲取到鎖時才會被喚醒。休眠和喚醒的代價很高,特別是在頻繁鎖定/解鎖的場景中會影響性能。
適用場景上的區別:
自旋鎖常用于內核中或者需要避免上下文切換的場景。特別是在中斷上下文中,自旋鎖更為合適,因為中斷處理程序不能被阻塞或休眠。它適用于那些執行時間極短的臨界區,鎖的持有時間必須足夠短,才能避免因自旋導致CPU資源浪費。互斥鎖更適用于用戶態的程序或者鎖定時間較長的臨界區。當程序無法獲取到互斥鎖時,系統可以調度其他線程運行,直到鎖被釋放,適合長時間的等待操作。
死鎖問題:
如果對同一個自旋鎖進行兩次加鎖操作,必然會導致死鎖,因為自旋鎖不具備遞歸性。互斥鎖則可以通過特定的類型來避免死鎖。例如PTHREAD_MUTEX_ERRORCHECK類型的互斥鎖在檢測到重復加鎖時,會返回錯誤而不是陷入死鎖狀態。
1、自旋鎖初始化與銷毀
自旋鎖需要在使用前進行初始化,并在不再使用時銷毀。
初始化自旋鎖函數如下:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
參數:
- lock:指向需要初始化的自旋鎖對象。
- pshared:自旋鎖的共享屬性,可以取值:
- PTHREAD_PROCESS_SHARED:允許在多個進程中的線程之間共享自旋鎖。
- PTHREAD_PROCESS_PRIVATE:自旋鎖只能在同一進程內的線程之間使用。
返回值:成功時返回0,失敗時返回非零錯誤碼。
銷毀自旋鎖函數如下:
int pthread_spin_destroy(pthread_spinlock_t *lock);
參數:lock:指向要銷毀的自旋鎖對象。
返回值:成功時返回0,失敗時返回非零錯誤碼。
2、自旋鎖加鎖與解鎖
加鎖函數如下:
int pthread_spin_lock(pthread_spinlock_t *lock);
參數:lock:指向要加鎖的自旋鎖對象。
返回值:成功時返回0;如果鎖已經被其他線程占用,則線程會忙等,直到成功獲取鎖,最終返回0。
嘗試加鎖函數如下:
int pthread_spin_trylock(pthread_spinlock_t *lock);
參數:lock:指向要加鎖的自旋鎖對象。
返回值:
- 成功時返回0。
- 如果鎖已被占用,立即返回EBUSY。
解鎖函數如下:
int pthread_spin_unlock(pthread_spinlock_t *lock);
參數:lock:指向要解鎖的自旋鎖對象。
返回值:成功時返回0,失敗時返回非零錯誤碼。
下面是一個完整的示例,展示如何使用自旋鎖,包括初始化、加鎖、解鎖和銷毀:
pthread_spinlock_t spinlock; // 定義自旋鎖 int shared_data = 0; // 共享數據 void *thread_func(void *arg) { pthread_spin_lock(&spinlock); // 加鎖 shared_data++; printf("Thread %ld: shared_data = %dn", (long)arg, shared_data); pthread_spin_unlock(&spinlock); // 解鎖 return NULL; } int main() { pthread_t threads[2]; // 初始化自旋鎖 if (pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE) != 0) { perror("Failed to initialize spinlock"); return 1; } // 創建兩個線程 pthread_create(&threads[0], NULL, thread_func, (void *)1); pthread_create(&threads[1], NULL, thread_func, (void *)2); // 等待線程結束 pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); // 銷毀自旋鎖 if (pthread_spin_destroy(&spinlock) != 0) { perror("Failed to destroy spinlock"); return 1; } return 0; }
自旋鎖的主要問題在于,它在獲取不到鎖時不會釋放CPU,而是持續消耗資源。如果鎖持有時間較長,CPU的利用效率會急劇下降。因此,自旋鎖不適合用于長時間鎖定的場景,只適合那些臨界區極短的操作。
通過對pthreadspin*函數的合理使用,可以有效管理多線程訪問共享資源的同步問題。確保在適當的地方進行加鎖和解鎖,以防止死鎖和資源競爭。