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