多線程問題在面試中經常遇到,比如在面試瑞晟過程中就被重點問到了多線程的知識。
1、以下多線程對int型變量x的操作,哪幾個不需要進行同步(D)
A. x=y; B. x++; C. ++x; D. x=1;
2、編寫一個程序,開啓3個線程,這3個線程的ID分別爲A、B、C,每個線程將自己的ID在屏幕上打印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC….依次遞推?
#include "stdio.h"
#include "stdlib.h"
#include <iostream>
#include <string>
#include <stack>
#include <windows.h>
#include <process.h>
#include<iostream>
using namespace std;
const int Numbers = 10;
HANDLE A,B,C;
int times=1;
unsigned int __stdcall FunA(void *pPM)
{
Sleep(100);//some work should to do
printf("第%d次:\n",times);
times++;
printf("A\n");
ReleaseSemaphore(B, 1, NULL);//遞增信號量B的資源數
return 0;
}
unsigned int __stdcall FunB(void *pPM)
{
Sleep(100);
printf("B\n");
ReleaseSemaphore(C, 1, NULL);//遞增信號量C的資源數
return 0;
}
unsigned int __stdcall FunC(void *pPM)
{
Sleep(100);
printf("C\n");
ReleaseSemaphore(A, 1, NULL);//遞增信號量A的資源數
return 0;
}
int main()
{
//初始化信號量
A = CreateSemaphore(NULL, 1, 1, NULL);//當前1個資源,最大允許1個同時訪問
B = CreateSemaphore(NULL, 0, 1, NULL);//當前0個資源,最大允許1個同時訪問
C = CreateSemaphore(NULL, 0, 1, NULL);//當前0個資源,最大允許1個同時訪問
HANDLE handle[Numbers];
int i = 0;
while (i < Numbers)
{
WaitForSingleObject(A, INFINITE); //等待信號量A>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunA, &i, 0, NULL);
WaitForSingleObject(B, INFINITE); //等待信號量B>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunB, &i, 0, NULL);
WaitForSingleObject(C, INFINITE); //等待信號量C>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunC, &i, 0, NULL);
++i;
}
WaitForMultipleObjects(Numbers, handle, TRUE, INFINITE);
//銷燬信號量
CloseHandle(A);
CloseHandle(B);
CloseHandle(C);
for (i = 0; i < Numbers; i++)
CloseHandle(handle[i]);
return 0;
}
運行結果如下:
3、 線程的基本概念、線程的基本狀態及狀態之間的關係?
答:線程是進程中的一個執行控制單元,執行路徑,一個進程中至少有一個線程在負責控制程序的執行一個進程中如果只有一個執行路徑,這個程序稱爲單線程一個進程中有多個執行路徑時,這個程序成爲多線程。
線程有四種狀態:新生狀態、可運行狀態、被阻塞狀態、死亡狀態
2、 線程與進程的區別?
答:a.進程是資源分配的基本單位,線程是cpu調度,或者說是程序執行的最小單位
b.進程有獨立的地址空間,系統必須分配給它獨立的地址空間,建立衆多的數據表來維護它的代碼段、堆棧段和數據段,而運行一個進程中的線程,它們之間共享大部分數據,使用相同的地址空間,當然,線程是擁有自己的局部變量和堆棧(注意不是堆)的。
c.線程之間的通信比較方便。統一進程下的線程共享數據(比如全局變量,靜態變量),通過這些數據來通信不僅快捷而且方便,當然如何處理好這些訪問的同步與互斥正是編寫多線程程序的難點。而進程之間的通信只能通過進程通信的方式進行。
3、多線程有幾種實現方法,都是什麼?
答:(1)通過操作系統API;
(2)使用標準C++線程支持庫;
(3)使用第三方提供的線程庫;
4、多線程同步和互斥有幾種實現方法,都是什麼?
答:線程間的同步方法大體可分爲兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態,而用戶模式就是不需要切換到內核態,只在用戶態完成操作。
用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區。內核模式下的方法有:事件,信號量,互斥量。
(1)臨界區(CCriticalSection)
當多個線程訪問一個獨佔性共享資源時,可以使用臨界區對象。擁有臨界區的線程可以訪問被保護起來的資源或代碼段,其他線程若想訪問,則被掛起,直到擁有臨界區的線程放棄臨界區爲止。具體應用方式:
1、定義臨界區對象CcriticalSection g_CriticalSection;
2、在訪問共享資源(代碼或變量)之前,先獲得臨界區對象,g_CriticalSection.Lock();
3、訪問共享資源後,則放棄臨界區對象,g_CriticalSection.Unlock();
(2)事件(CEvent)
事件機制,則允許一個線程在處理完一個任務後,主動喚醒另外一個線程執行任務。比如在某些網絡應用程序中,一個線程如A負責偵聽通信端口,另外一個線程B負責更新用戶數據,利用事件機制,則線程A可以通知線程B何時更新用戶數據。每個Cevent對象可以有兩種狀態:有信號狀態和無信號狀態。Cevent類對象有兩種類型:人工事件和自動事件。
自動事件對象,在被至少一個線程釋放後自動返回到無信號狀態;
人工事件對象,獲得信號後,釋放可利用線程,但直到調用成員函數ReSet()纔將其設置爲無信號狀態。在創建Cevent對象時,默認創建的是自動事件。一般通過調用WaitForSingleObject()函數來監視事件狀態。
(3)互斥量(CMutex)
互斥對象和臨界區對象非常相似,只是其允許在進程間使用,而臨界區只限制與同一進程的各個線程之間使用,但是更節省資源,更有效率。
(4)信號量(CSemphore)
當需要一個計數器來限制可以使用某共享資源的線程數目時,可以使用“信號量”對象。CSemaphore類對象保存了對當前訪問某一個指定資源的線程的計數值,該計數值是當前還可以使用該資源的線程數目。如果這個計數達到了零,則所有對這個CSemaphore類對象所控制的資源的訪問嘗試都被放入到一個隊列中等待,直到超時或計數值不爲零爲止。
5、多線程同步和互斥有何異同,什麼情況下分別使用他們?舉例說明
答:線程同步是指線程之間所具有的一種制約關係,一個線程的執行依賴另一個線程的消息,當它沒有得到另一個線程的消息時應等待,直到消息到達時才被喚醒。
線程互斥是指對於共享的進程系統資源,在各用該資源的線程必須等待,直到佔用資源者釋放該資源。線程互斥可以看成是單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多隻允許一個線程去使用,其它要使一種特殊的線程同步(下文統稱爲同步)。
6、在Windows編程中互斥量與臨界區比較類似,請分析一下二者的主要區別
答:1)互斥量是內核對象,所以它比臨界區更加耗費資源,但是它可以命名,因此可以被其它進程訪問
2)從目的是來說,臨界區是通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。 互斥量是爲協調共同對一個共享資源的單獨訪問而設計的。
7、進程與線程?
答:進程由兩個部分組成:1)操作系統用來管理進程的內核對象。內核對象也是系統用來存放關於進程的統計信息的地方。2)地址空間。它包含所有可執行模塊或DLL模塊的代碼和數據。它還包含動態內存分配的空間。如線程堆棧和堆分配空間。
線程由兩個部分組成:1)線程的內核對象,操作系統用它來對線程實施管理。內核對象也是系統用來存放線程統計信息的地方。2)線程堆棧,它用於維護線程在執行代碼時需要的所有參數和局部變量。當創建線程時,系統創建一個線程內核對象。該線程內核對象不是線程本身,而是操作系統用來管理線程的較小的數據結構。可以將線程內核對象視爲由關於線程的統計信息組成的一個小型數據結構。
8、進程間的通信?
答:進程間通信是管道、內存共享、消息隊列、信號量、socket
管道分爲有名管道和無名管道,無名管道只能用於親屬進程之間的通信,而有名管道則可用於無親屬關係的進程之間。
消息隊列是用於兩個進程之間的通訊,首先在一個進程中創建一個消息隊列,然後再往消息隊列中寫數據,而另一個進程則從那個消息隊列中取數據。需要注意的是,消息隊列是用創建文件的方式建立的,如果一個進程向某個消息隊列中寫入了數據之後,另一個進程並沒有取出數據,即使向消息隊列中寫數據的進程已經結束,保存在消息隊列中的數據並沒有消失,也就是說下次再從這個消息隊列讀數據的時候,就是上次的數據!!!!
共享內存通常由一個進程創建,其餘進程對這塊內存區進行讀寫。得到共享內存有兩種方式:映射/dev/mem設備和內存映像文件。前一種方式不給系統帶來額外的開銷,但在現實中並不常用,因爲它控制存取的是實際的物理內存;
共享內存允許兩個或多個進程進程共享同一塊內存(這塊內存會映射到各個進程自己獨立的地址空間)從而使得這些進程可以相互通信。在GNU/Linux中所有的進程都有唯一的虛擬地址空間,而共享內存應用編程接口API允許一個進程使用公共內存區段。但是對內存的共享訪問其複雜度也相應增加。共享內存的優點是簡易性。使用消息隊列時,一個進程要向隊列中寫入消息,這要引起從用戶地址空間向內核地址空間的一次複製,同樣一個進程進行消息讀取時也要進行一次複製。共享內存的優點是完全省去了這些操作。共享內存會映射到進程的虛擬地址空間,進程對其可以直接訪問,避免了數據的複製過程。因此,共享內存是GNU/Linux現在可用的最快速的IPC機制。進程退出時會自動和已經掛接的共享內存區段分離,但是仍建議當進程不再使用共享區段時調用shmdt來卸載區段。注意,當一個進程分支出父進程和子進程時,父進程先前創建的所有共享內存區段都會被子進程繼承。如果區段已經做了刪除標記(在前面以IPC——RMID指令調用shmctl),而當前掛接數已經變爲0,這個區段就會被移除。
9、什麼是死鎖?其條件是什麼?怎樣避免死鎖?
答:死鎖的概念:在兩個或多個併發進程中,如果每個進程持有某種資源而又都等待別的進程釋放它或它們現在保持着的資源,在未改變這種狀態之前都不能向前推進,稱這一組進程產生了死鎖。通俗地講,就是兩個或多個進程被無限期地阻塞、相互等待的一種狀態。
產生死鎖的必要條件:
(1)互斥條件(mutualexclusion),一個資源每次只能被一個進程使用;
(2)不剝奪條件(nopreemption),進程已獲得的資源,在未使用完之前,不能強行剝奪;
(3)請求和保持條件(hold andwait),一個進程因請求資源而阻塞時,對已獲得的資源保持不放;
(4)環路等待條件(circularwait),若干進程之間形成一種首尾相接的循環等待資源關係。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
死鎖的解除與預防:理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確定資源的合理分配算法,避免進程永久佔據系統資源。此外,也要防止進程在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。
死鎖的處理策略:鴕鳥策略、預防策略、避免策略、檢測與恢復策略。
1、通過引入事務機制方法是將所有上鎖操作均作爲事務對待,一旦開始上鎖,即確保全部操作均可回退,同時通過鎖管理器檢測死鎖,並剝奪資源(回退事務)
2、 設置死鎖超時參數爲合理範圍,如:3分鐘-10分種;超過時間,自動放棄本次操作,避免進程懸掛;
3、 破壞環形等待條件使上鎖的順序必須一致。
10、互斥鎖和信號量的區別?
答:1. 互斥量用於線程的互斥,信號量用於線程的同步。
這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源
2. 互斥量值只能爲0/1,信號量值可以爲非負整數。
也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量爲單值信號量是,也可以完成一個資源的互斥訪問。
3. 互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。
信號量(Semaphore),有時被稱爲信號燈,是在多線程環境下使用的一種設施, 它負責協調各個線程, 以保證它們能夠正確、合理的使用公共資源。