文章目錄
- 前言
- 進程和線程有什么不同
- 進程,線程的通訊方式
- 什么是鎖
- 為什么說鎖可以使線程安全
- 加鎖有什么副作用
- 總結
前言
這是個人總結進程和線程方面的面試題。如果有錯,歡迎佬們前來指導!!!
進程和線程有什么不同
進程是程序的動態執行的實例,每個進程都有獨立的地址空間,資源(如文件描述符,內存地址空間)和系統狀態。
獨立性:進程之間資源隔離,一個進程崩潰不會直接影響到其它進程
唯一標識:通過PID和PPID標識,形成樹狀結構(init為根進程)
動態性:生命周期包括創建(fork()),運行,阻塞,終止等狀態變化
線程是進程內的執行單元,共享進程的資源(如內存,文件描述符),但擁有獨立的棧和寄存器
輕量級:線程創建和切換的開銷遠小于進程
共享性:線程可直接訪問進程的全局變量,通信更高效,但也需要同步機制(如互斥鎖)來避免競爭
調度單位:線程是CPU調度的最小單位,進程是資源分配的最小單位
資源分配差異
進程是操作系統資源分配的基本單位,每個進程都有獨立的內存地址空間(代碼段,數據段,堆棧段),文件描述符等資源。這意味著每個進程在內存中都有自己的私有數據和執行環境,互補影響。
線程是進程內的執行流,共享同一進程的資源,包括內存空間,文件描述符等。多個線程可以訪問同一個進程的內存空間,因此線程之間的通信和數據共享更加方便
調度機制
進程是操作系統調度的基本單位,由操作系統進行調度管理。線程是CPU調度的基本單位,由內核調度器負責。
進程切換:需要切換虛擬地址空間,寄存器,文件描述符等,開銷大。
線程切換:僅需切換棧和寄存器,共享地址空間,開銷小。
創建與銷毀
創建進程需分配獨立內存,初始化PCB,調用fork()系統調用;銷毀進程需要回收全部資源,耗時較長。
創建線程僅需分配棧和線程控制塊,調用pthread_create()庫函數;銷毀線程僅釋放棧和線程控制塊,資源回收簡單。
通信機制對比
進程需通過IPC機制:管道,消息隊列,共享內存,信號量來實現進程間通信,同步需求低(資源獨立)。
線程直接通過共享內存(全局變量,堆)無需額外機制,同步需求高(需互斥鎖,條件變量等避免數據競爭)。
進程,線程的通訊方式
進程間通信方式
- 管道(pipe)
匿名管道:基于內存的FIFO隊列,僅限于父子進程或兄弟進程間單向通信。通過pipe()系統調用創建,讀寫端由不同進程分別關閉
命名管道:通過文件系統路徑標識,允許無親緣關系的進程通信。使用mkfifo()創建,支持雙向通信但需自行管理同步
特定:簡單高效,但容量有限且僅支持半雙工,適合簡單數據流傳輸- 消息隊列(message queue)
結構:內核維護的鏈表結構,消息按FIFO或優先級排序存儲,支持多進程異步讀寫。
使用場景:適合需要解耦生產者和消費者的場景(如任務分布系統),支持支持化消息和確認機制,但需處理消息丟失或重復問題
如System V的msgget/msgsnd/msgrcv或POSIX的mq_open/mq_send/mq_receive- 共享內存(shared memory)
原理:多個進程映射同一個物理內存區域,直接讀寫數據。
同步機制:
信號量:通過PV操作控制訪問權限,如System V的semp或POSIX信號量
互斥鎖/條件變量:在共享內存中嵌入鎖結構,配合線程同步
優勢:速度最快,適合大數據量高頻通信(如視頻處理);缺點是需要顯示同步,編程復雜度高。- 信號(signal)
用途:異步通知進程特定事件(如SIGKILL終止進程)。通過kill()或signal()發送和處理
局限性:信號編號少,不適合復雜數據傳遞,多用于進程控制- 套接字(socket)
跨進程特性:支持本地和網絡通信。通過socket()/bind()/listen()/accept()建立連接,實現全雙工通信
應用場景:分布式系統,C/S架構,需要處理序列化和協議設計- 信號量(semaphores)
功能:計數器機制協調資源訪問,解決互斥與同步問題。分為System V信號量集和POSIX信號量
如初始化信號量為1(互斥鎖),進程訪問共享資源前執行sem_wait(),是否后執行sem_post()。
線程共享進程資源,通信更側重同步而非數據傳輸
- 共享變量與鎖機制
互斥鎖(Mutex):確保臨界區原子性。使用pthread_mutex_init()初始化,lock()/unlock()包含資源
讀寫鎖(Read-Write-Lock):允許多讀單寫,提高并發性。通過pthread_rwlock實現,適用于讀多寫少場景- 條件變量(Condition Variable)
協作機制:線程等待特定條件成立(如資源就緒)。需與互斥鎖配合,使用pthread_cond_wait()掛其線程,pthread_cond_signal()喚醒- 信號量(Semaphores)
線程級同步:與進程間信號類似,但作用域限于同一進程。POSIX無名信號量通過sem_init()初始化。
信號量的互斥與同步作用
使用信號量實現進程間的互斥與同步主要通過以下步驟:
- 初始化信號量:創建一個信號量并初始化其值。對于互斥訪問,信號量通常初始化為1。列如,使用sem_init函數初始化信號量,或使用sem_open創建命名信號量并初始化
- 進入臨界區前執行P操作:進程在進入臨界區前執行P操作(等待操作),即sem_wait或semop函數。如果信號量大于0,則將其減1并允許進程進入臨界區;如果信號量值為0,則進程被阻塞,直到信號量值大于0
- 退出臨界區后執行V操作:進程在提出臨界區后執行V操作(信號操作),即sem_post或semp函數。這將信號量加1,并喚醒一個等待該信號量的進程
- 銷毀信號量:在不再需要信號量時,使用sem_destory或sem_unlink函數銷毀信號量,釋放相關資源
通過上訴步驟,信號量可以確保多個進程對共享資源的互斥訪問,避免數據競爭和不一致。
什么是鎖
鎖是一種用于同步并發訪問共享資源的機制,通過限制對共享資源的訪問順序,確保在任意時刻只有一個線程能進入臨界區。其本質是內存中的一個整形變量,通過狀態(如0表示空閑,1表示鎖定)實現資源占用的原子控制。在多線程環境下,鎖的作用包括:
1. 互斥訪問:防止多個線程同時修改空閑數據,導致不可預測的結果
2. 數據一致性:確保線程操作空閑資源時的中間狀態對其它線程不可見
3. 執行順序控制:通過信號量等機制協調線程的執行流程
在linux系統中,常見的鎖類型包括互斥鎖(mutex),讀寫鎖(rwlock),自旋鎖(spinlock),信號量(semaphore)。
互斥鎖(Mutex)
定義:互斥鎖是最基本且最常用的鎖類型,用于保護共享資源,確保在任何時候只有一個線程或進程可以訪問該資源
實現:通過pthread_mutex_t類型實現,可以通過靜態或動態方式創建。靜態創建使用PTHREAD_MUTEX_INITALIZER,動態創建使用pthread_mutex_init函數。
操作:主要操作包括pthread_mutex_lock(獲取鎖),pthread_mutex_unlock(釋放鎖)和pthread_mutex_destroy(銷毀鎖)讀寫鎖(RWLock)
定義:讀寫鎖允許多個線程同時讀取共享資源,但只允許一個線程寫入資源。這樣在讀操作頻繁而寫操作較少的場景下可以顯著提高并發性能
實現:通過pthread_rwlock_t類型實現,可以通過靜態或動態方式創建。靜態創建使用PTHREAD_RWLOCK_INITALIZER,動態創建使用pthread_rwlock_init函數。
操作:主頁操作包括pthread_rwlock_rdlock(獲取讀鎖),pthread_rwlock_wrlock(獲取寫鎖),pthread_rwlock_unlock(釋放鎖)和pthread_rwlock_destroy(銷毀鎖)信號量(Semaphore):
定義:信號量用于控制多個進程或線程對共享資源的訪問,實現更復雜的同步需求
實現:通過sem_t類型實現,可以通過靜態或動態方式創建。靜態創建使用SEM_FAILED,動態創建使用sem_init函數
操作:主要操作包括sem_wait(獲取信號量),sem_post(釋放信號量)和sem_destroy(銷毀信號量)
為什么說鎖可以使線程安全
線程安全是指在多線程環境下,多個線程同時訪問共享資源時,能夠正確的處理共享數據,保證數據的一致性和正確性,而不會導致不確定的結果或產生競態條件。具體來說,線程安全的代碼在多線程并發執行時,能夠按照預期正確運行,不會因為共享資源的并發訪問而引發錯誤。
線程安全的特點:
1.原子性:對共享資源的操作是原子的,要么完全執行,要么不執行,不能被中斷或分割
2.可見性:一個線程對共享變量的修改,其他線程能夠立即看到
3.有序性:程序執行的順序按照代碼的先后順序進行,避免指令重排序導致的問題
常見的線程安全問題
1.競態條件:多個線程同時修改同一變量,導致結果不確定
2.死鎖:兩個或多個線程在執行過程中,因爭奪資源而造成的一種僵局
3.活鎖:兩個或多個線程在執行過程中,因爭奪資源而不斷重復嘗試,但都無法取的進展
4.饑餓:某個線程無法獲得必要的資源或條件,導致無法繼續執行
鎖通過以下機制解決多線程并發問題:
1. 強制互斥訪問臨界區:
鎖確保同一時間僅有一個線程進入臨界區,其它線程需等待鎖釋放。解決了原子性問題,避免多個線程同時修改共享資源(互斥鎖通過原子操作和等待隊列,確保臨界區代碼串行執行)
2. 內存屏障(?):
鎖的獲取和釋放操作隱含內存屏障,強制刷新緩存并阻止指令重排,從而保證可見性和有序性
3. 防止競態條件:
競態條件源于多個線程對共享資源的非協調訪問。鎖通過強制同步,將并發操作轉換為順序執行,消除執行順序的不確定性(若兩個線程同時執行a++,未加鎖可能因非原子操作導致結果錯誤;加鎖后,操作變為原子的,結果正確)
加鎖有什么副作用
性能開銷:
鎖的獲取和釋放操作本身需要消耗系統資源,可能成為程序性能瓶頸
- 原子操作與上下切換
原子指令開銷:鎖的底層依賴CPU的原子指令(如xchg, cmpxchg),這些指令會觸發總線鎖定或緩存同步,導致CPU流水線暫停,降低指令級并行性
上下文切換:互斥鎖(Mutex)在鎖競爭時會讓線程進入睡眠狀態(通過futex系統調用),觸發線程調度和上下文切換。若鎖競爭激烈,頻繁的切換會顯著增加CPU開銷- 自旋鎖的CPU浪費:
自旋鎖(Spinlock)通過忙等待(Busy Waiting)獲取鎖,若鎖持有時間較長,會持續占用CPU核心,浪費計算資源(單核CPU陷阱:在單核系統中,自旋鎖可能導致死鎖)- 緩存失效:
多個線程競爭同一鎖時,鎖變量的緩存行會在不同CPU核心之間頻繁無效化,導致緩存一致性協議的額外開銷
死鎖
不合理的鎖使用可能導致死鎖,即多個線程互相等待對方釋放鎖,程序永久阻塞。死鎖的四個必要條件:
1. 互斥訪問:鎖本身是獨占資源
2. 持有并等待:線程持有鎖的同時請求其它鎖
3. 不可剝奪:鎖只能由持有者釋放
4. 循環等待:線程間形成環形等待鏈
活鎖
活鎖是死鎖的一種變體:線程不斷嘗試獲取鎖失敗,但未進入阻塞狀態,導致CPU資源浪費(兩個線程同時檢測到對方持有鎖,主動釋放自己的鎖并重試,形成無限循環)
其它副作用:
1. 資源泄漏:忘記釋放鎖導致其它線程永久阻塞
2. 錯誤處理復雜性:臨界區內的代碼若拋出異常或提取返回,需確保鎖被釋放(可通過RAII模式解決,如C++的std::lock_guard)
3. 鎖與信號的交互:信號處理函數中不可使用非異步信號安全函數,否則可能引發死鎖
總結
以上就是我總結的C++面試題,進程和線程方面(1)