多線程應(yīng)用程序并行性
通常,在多線程應(yīng)用程序中有兩種相互關(guān)聯(lián)但不同的現(xiàn)象:并發(fā)性和并行性。
并發(fā)是兩個或多個線程在執(zhí)行中重疊的能力。
并行性是同時執(zhí)行兩個或多個線程的能力。
正是并發(fā)性導致了流式處理中的大多數(shù)復雜性 - 線程可以以不可預測的順序執(zhí)行。在線程共享資源的情況下,這無疑會導致競爭條件。
術(shù)語爭用條件通常是指對兩個和多個線程的共享資源的不同步訪問導致錯誤的程序行為的情況。
讓我們看一個比賽的例子。
如今,很難想象我們沒有塑料卡的生活。ATM取款很久以前就成為日常工作:插入卡,輸入PIN碼和所需的金額。如果成功完成,我們將收到計劃的現(xiàn)金金額。反過來,銀行需要通過以下算法驗證資金是否可用:
銀行賬戶上是否至少有 X 個單位的可用貨幣?
如果是,將帳戶余額減少 X 值,向用戶分配 X 個貨幣單位。
否則,將生成錯誤消息。
具有爭用條件的代碼示例:
int cash_out(struct account *ac, int amount) {
const int balance = ac->balance;
if (balance < amount)
return -1;
ac->balance = balance - amount;
discard_money_routine(amount);
return 0;
}
當在線支付購買并“同時”從 ATM 提取現(xiàn)金時,可能會出現(xiàn)種族。
為了避免比賽,有必要對代碼進行以下更新:
int cash_out(struct account *ac, int amount) {
lock();
const int balance = ac->balance;
if (balance < amount)
return -1;
ac->balance = balance - amount;
unlock();
discard_money_routine(amount);
return 0;
}
在Windows操作系統(tǒng)中,需要獨占訪問某些共享數(shù)據(jù)的代碼區(qū)域稱為“關(guān)鍵部分”。
用于處理關(guān)鍵部分的結(jié)構(gòu)類型為CRITICAL_SECTION。讓我們回顧一下它的字段:
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // from the thread's ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
盡管CRITICAL_SECTION正式不屬于未記錄的結(jié)構(gòu),但微軟仍然認為用戶無需了解其組織。實際上,它是一種黑匣子。要使用此結(jié)構(gòu),無需直接使用其字段,而只需通過 Windows 函數(shù),將此結(jié)構(gòu)的相應(yīng)實例的地址傳遞給它們。
CRITICAL_SECTION結(jié)構(gòu)通過以下調(diào)用初始化:
void 初始化關(guān)鍵部分(PCRITICAL_SECTION 個);
如果我們知道不再需要CRITICAL_SECTION結(jié)構(gòu),那么我們可以借助以下調(diào)用將其刪除:
無效刪除關(guān)鍵部分(PCRITICAL_SECTION個);
使用共享資源的代碼區(qū)域應(yīng)先進行以下調(diào)用:
無效的進入臨界部分(PCRITICAL_SECTION個);
我們可以使用以下命令代替EnterCriticalSection:
bool 嘗試輸入關(guān)鍵部分(PCRITICAL_SECTION 個);
TryEnterCriticalSection允許線程檢查資源可訪問性,并在無法訪問時參與另一個活動。在成功的情況下(函數(shù)返回TRUE),很明顯結(jié)構(gòu)元素已更新,資源已鎖定。
在使用共享資源的代碼區(qū)域末尾,應(yīng)始終存在以下調(diào)用:
void LeaveCriticalSection(PCRITICAL_SECTION pcs);
LeaveCriticalSection檢查CRITICAL_SECTION結(jié)構(gòu)元素,并將資源鎖定計數(shù)器 (LockCount) 減少 1。
類似于 Linux 操作系統(tǒng)中的CRITICAL_SECTION是可變互斥pthread_mutex_t。在使用之前,需要初始化此變量 – 寫入常量PTHREAD_MUTEX_INITIALIZER的值或調(diào)用pthread_mutex_init函數(shù)。
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
要使用默認屬性值初始化互斥鎖,必須將NULL傳遞給attr屬性。可以在幫助頁面上找到特定的互斥鎖屬性值。
可以通過以下調(diào)用刪除互斥鎖:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
通過調(diào)用pthread_mutex_lock函數(shù)鎖定互斥鎖:
int pthread_mutex_lock(pthread_mutex_t *mutex);
如果互斥鎖已被鎖定,則調(diào)用線程將被阻塞,直到釋放互斥鎖。互斥鎖在pthread_mutex_unlock功能的幫助下解鎖:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
如果我們想檢查資源的可訪問性,那么我們可以使用pthread_mutex_trylock函數(shù):
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果互斥鎖被鎖定,上述函數(shù)將返回EBUSY。
所有用于處理互斥鎖的函數(shù)在成功時返回 0,在失敗時返回錯誤代碼。
讓我們總結(jié)一下。在 Windows 操作系統(tǒng)中,要使用共享資源,必須使用關(guān)鍵部分和特殊類型的CRITICAL_SECTION。在 Linux 操作系統(tǒng)中,我們可以出于相同目的使用pthread_mutex_t類型的互斥體。
同步功能記錄在表 4 中。
窗口函數(shù) | Linux函數(shù) |
初始化關(guān)鍵部分 | pthread_mutex_init() |
進入關(guān)鍵部分 | pthread_mutex_lock() |
離開關(guān)鍵部分 | pthread_mutex_unlock() |
嘗試進入關(guān)鍵部分 | pthread_mutex_trylock() |
刪除關(guān)鍵部分 | pthread_mutex_destroy() |
表 4.共享資源的同步功能。
螺紋端接
在實踐中,需要編寫線程終止的情況之一是海量數(shù)據(jù)處理。當主線程向所有線程發(fā)出退出信號,但其中一個線程仍在處理信息時,可能會出現(xiàn)這種情況。如果與信息丟失相比,及時性是應(yīng)用程序性能的更高優(yōu)先級因素,則需要退出線程并釋放系統(tǒng)資源。本節(jié)將介紹退出線程的方法。
線程可以通過以下方式退出:
線程函數(shù)返回
線程調(diào)用 ExitThread 函數(shù)
進程的任何線程都調(diào)用 TerminateThread 函數(shù)
進程的任何線程都調(diào)用 ExitProcess 函數(shù)
讓我們仔細看看其中的每一個。
線程函數(shù)返回。
干凈代碼的一個很好的例子是設(shè)計線程函數(shù),以便線程僅在函數(shù)返回后終止。在 Windows 操作系統(tǒng)中,這種線程終止方式保證正確清理線程擁有的資源。在 Linux 操作系統(tǒng)中,在線程可連接的情況下,必須調(diào)用其中一個連接函數(shù)。在一般情況下,會發(fā)生以下情況:
系統(tǒng)正確釋放線程占用的資源。
系統(tǒng)設(shè)置線程退出代碼。
此內(nèi)核對象 ?thread? 的用戶計數(shù)器減少 1。
在 Windows 操作系統(tǒng)中,可以通過調(diào)用以下內(nèi)容來強制終止線程:
void ExitThread(DWORD dwExitCode);
線程退出代碼值將添加到dwExitCode參數(shù)中。很容易注意到該函數(shù)沒有返回值,因為在調(diào)用該函數(shù)后,線程將不復存在。
在Linux操作系統(tǒng)中,有一個完整的ExitThread模擬:
void pthread_exit(void *rval_ptr);
參數(shù)rval_ptr表示包含返回值的非類型指針。此指針可由調(diào)用pthread_join函數(shù)的其他進程線程獲取。
函數(shù)調(diào)用pthread_join將線程帶到分離狀態(tài)。此狀態(tài)允許贏回線程資源。如果線程已處于分離狀態(tài),則調(diào)用pthread_join的線程將收到ESRCH錯誤代碼。有時,當使用第二個非NULL參數(shù)調(diào)用pthread_join時,可能會輸出分段錯誤錯誤。
進程的任何線程都調(diào)用 TerminateThread 函數(shù)。
一個線程可以傳遞請求以強制終止同一進程中的另一個線程。在Windows操作系統(tǒng)中,這是在以下功能的幫助下組織的:
bool TerminateThread(
HANDLE hThread,
DWORD dwExitCode
);
上述函數(shù)從任何其他線程終止了 hThread線程。您可以向dwExitCode參數(shù)添加一個值,系統(tǒng)將視之為線程退出代碼。線程被殺死后,此內(nèi)核對象 ?thread? 的用戶計數(shù)器將減少 1。
在 Linux 操作系統(tǒng)中,當一個線程可以通過調(diào)用pthread_cancel函數(shù)傳遞強制終止同一進程中另一個線程的請求時,可以實現(xiàn)類似的功能:
int pthread_cancel(pthread_t tid);
此函數(shù)需要與pthread_setcancelstate和pthread_setcanceltype函數(shù)結(jié)合使用。如果使用pthread_cancel,rval_ptr將被PTHREAD_CANCELED。
讓我們仔細看看erminateThread和 Linux 操作系統(tǒng)中的類似操作:
#ifdef __PL_WINDOWS__
BOOL bret = FALSE;
bret = TerminateThread(h, x);
#endif //__PL_WINDOWS__
#ifdef __PL_LINUX__
int iret = 0, bret;
iret = syscall(SYS_tkill,tid, 0);
if (iret == 0) {
iret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
if (iret != 0) {
bret = FALSE;
}
else {
iret = pthread_cancel(h);
if (iret == 0 || iret == ESRCH) {
bret = TRUE;
} else {
wait_thread:
clock_gettime(CLOCK_REALTIME, &wait_time);
ADD_MS_TO_TIMESPEC(wait_time, 1000); //1000 ms
iret = pthread_timedjoin_np(h, NULL, &wait_time);
switch (iret) {
case 0:
bret = TRUE;
break;
case ETIMEDOUT:
if (retries_count++ < 5) // 5 Attempts
{
goto wait_thread;
}
bret = FALSE;
break;
default:
bret = FALSE;
break;
}
}
(void)pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
}
}
else {
bret = TRUE;
}
#endif //__PL_LINUX__
審核編輯:郭婷
-
Linux
+關(guān)注
關(guān)注
87文章
11329瀏覽量
209969 -
WINDOWS
+關(guān)注
關(guān)注
4文章
3554瀏覽量
89005 -
線程
+關(guān)注
關(guān)注
0文章
505瀏覽量
19715
發(fā)布評論請先 登錄
相關(guān)推薦
評論