《TCP/IP網絡編程》學習筆記 | Chapter 20:Windows 中的線程同步

《TCP/IP網絡編程》學習筆記 | Chapter 20:Windows 中的線程同步

  • 《TCP/IP網絡編程》學習筆記 | Chapter 20:Windows 中的線程同步
    • 用戶模式和內核模式
      • 用戶模式同步
      • 內核模式同步
    • 基于 CRITICAL_SECTION 的同步
    • 內核模式的同步方法
      • 基于互斥量對象的同步
      • 基于信號量對象的同步
      • 基于事件對象的同步
    • Windows 平臺下實現多線程服務器端
    • 習題
      • (1)關于 Windows 操作系統的用戶模式和內核模式的說法正確的是?
      • (2)判斷下列關于用戶模式同步和內核模式同步描述的正誤。
      • (3)本章示例SyncSema_win.c 的 Read 函數中,退出臨界區需要較長時間,請給出解決方案并實現。
      • (4)請將本章 SyncEvent_win.c 示例改為基于信號量的同步方式,并得出相同運行結果。

《TCP/IP網絡編程》學習筆記 | Chapter 20:Windows 中的線程同步

用戶模式和內核模式

Windows操作系統的運行方式是“雙模式操作”(Dual-mode Operation):

  • 用戶模式(User mode):運行應用程序的基本模式,禁止訪問物理設備,而且會限制訪問的內存區域。
  • 內核模式(Kernal mode):操作系統運行時的模式,不僅不會限制訪問的內存區域,而且訪問的硬件設備也不會受限。

實際上,在應用程序運行過程中,Windows操作系統不會一直停留在用戶模式,而是在用戶模式和內核模式之間切換。

例如,可以在Windows中創建線程。雖然創建線程的請求是由應用程序的函數調用完成,但實際創建線程的是操作系統。因此,創建線程的過程中無法避免向內核模式的轉換。

定義這2種模式主要是為了提高安全性。應用程序的運行時錯誤會破壞操作系統及各種資源。特別是C/C++可以進行指針運算,很容易發生這類問題。例如,因為錯誤的指針運算覆蓋了操作系統中存有重要數據的內存區域,這很可能引起操作系統崩潰。但實際上各位從未經歷過這類事件,因為用戶模式會保護與操作系統有關的內存區域。因此,即使遇到錯誤的指針運算也僅停止應用程序的運行,而不會影響操作系統。

總之,像線程這種伴隨著內核對象創建的資源創建過程中,都要默認經歷如下模式轉換過程:用戶模式→內核模式→用戶模式。

從用戶模式切換到內核模式是為了創建資源,從內核模式再次切換到用戶模式是為了執行應用程序的剩余部分。不僅是資源的創建,與內核對象有關的所有事務都在內核模式下進行。

模式切換對系統而言其實也是一種負擔,頻繁的模式切換會影響性能。

用戶模式同步

用戶模式同步是用戶模式下進行的同步,即無需操作系統的幫助而在應用程序級別進行的同步。

用戶模式同步的最大優點是——速度快。無需切換到內核模式,僅考慮這一點也比經歷內核模式切換的其他方法要快。而且使用方法相對簡單,因此,適當運用用戶模式同步并無壞處。

但因為這種同步方法不會借助操作系統的力量,其功能上存在一定局限性。稍后將介紹屬于用戶模式同步的、基于“CRITICAL_SECTION”的同步方法。

內核模式同步

下面給出內核模式同步的優點。

  • 比用戶模式同步提供的功能更多。
  • 可以指定超時,防止產生死鎖。

因為都是通過操作系統的幫助完成同步的,所以提供更多功能。特別是在內核模式同步中,可以跨越進程進行線程同步。因為內核對象并不屬于某一進程,而是操作系統擁有并管理的。

與此同時,由于無法避免用戶模式和內核模式之間的切換,所以性能上會受到一定影響。

基于 CRITICAL_SECTION 的同步

基于 CRITICAL_SECTION 的同步中將創建并運用“CRITICAL_SECTION對象”,但這并非內核對象。與其他同步對象相同,它是進入臨界區的一把“鑰匙”。因此,為了進入臨界區,需要得到 CRITICAL_SECTION 對象這把“鑰匙”。相反,離開時應上交 CRITICAL_SECTION 對象。

下面介紹 CRITICAL_SECTION 對象的初始化及銷毀相關函數。

#include<windows.h>void InitilizerCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

參數:

  • IpCriticalSection:InitilizerCriticalSection 函數中傳入需要初始化的 CRITICAL_SECTION 對象的地址值,DeleteCriticalSection 函數中傳入需要解除的 CRITICAL_SECTION 對象的地址值。

上述函數的參數類型 LPCRITICAL_SECTION 是 CRITICAL_SECTION 指針類型。另外 DeleteCriticalSection 函數并不銷毀CRITICAL_SECTION 對象。該函數的作用是銷毀 CRITICAL_SECTION 對象相關的資源。

接下來介紹獲取(擁有)及釋放 CRITICAL_SECTION 對象的函數,可以簡單理解為獲取和釋放“鑰匙”的函數。

#include<windows.h>void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

參數:

  • IpCriticalSection:獲取(擁有)及釋放 CRITICAL_SECTION 對象的地址值。

與 Linux 部分中介紹過的互斥量類似,相信大部分人僅靠這些函數介紹也能寫出示例程序。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>#define NUM_THREAD 50
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDes(void *arg);long long num = 0;
CRITICAL_SECTION cs;int main(int argc, char *argv[])
{HANDLE tHandles[NUM_THREAD];int i;InitializeCriticalSection(&cs);for (i = 0; i < NUM_THREAD; i++){if (i % 2)tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);DeleteCriticalSection(&cs);printf("result: %lld \n", num);system("pause");return 0;
}unsigned WINAPI threadInc(void *arg)
{int i;EnterCriticalSection(&cs);for (i = 0; i < 50000000; i++)num += 1;LeaveCriticalSection(&cs);return 0;
}unsigned WINAPI threadDes(void *arg)
{int i;EnterCriticalSection(&cs);for (i = 0; i < 50000000; i++)num -= 1;LeaveCriticalSection(&cs);return 0;
}

運行結果:

在這里插入圖片描述

程序將整個循環納入臨界區,可以減少運行時間。

內核模式的同步方法

基于互斥量對象的同步

基于互斥量(Mutual Exclusion)對象的同步方法與基于 CRITICAL_SECTION 對象的同步方法類似,因此,互斥量對象同樣可以理解為“鑰匙”。

首先介紹創建互斥量對象的函數:

#include<windows.h>HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName
);

參數:

  • lpMutexAttributes:傳遞安全相關的配置信息,使用默認安全設置時可以傳遞 NULL。
  • blnitialOwner:如果為 TRUE,則創建出的互斥量對象屬于調用該函數的線程,同時進入 non-signaled 狀態;如果為 FALSE,則創建出的互斥量對象不屬于任何線程,此時狀態為 signaled。
  • IpName:用于命名互斥量對象。傳入 NULL 創建無名的互斥量對象。

成功時返回創建的互斥量對象句柄,失敗時返回 NULL。

從上述參數說明中可以看到,如果互斥量對象不屬于任何擁有者,則將進入 signaled 狀態。利用該特點進行同步。

另外,互斥量屬于內核對象,所以通過如下函數銷毀:

#include<windows.h>BOOL CloseHandle(HANDLE hObject);

參數:

  • hObject:要銷毀的內核對象的句柄。

成功時返回 TRUE,失敗時返回 FALSE。

上述函數是銷毀內核對象的函數,所以同樣可以銷毀即將介紹的信號量及事件。下面介紹獲取和釋放互斥量的函數,但我認為只需介紹釋放的函數,因為獲取是通過各位熟悉的 WaitForSingleObject 函數完成的。

#include<windows.h>BOOL ReleaseMutex(HANDLE hMutex);

參數:

  • hMutex:需要釋放的互斥量對象句柄。

成功時返回 TRUE,失敗時返回 FALSE。

接下來分析獲取和釋放互斥量的過程。互斥量被某一線程獲取時(擁有時)為 non-signaled 狀態,釋放時(未擁有時)進入 signaled 狀態。因此,可以使用 WaitForSingleObject 函數驗證互斥量是否已分配。該函數的調用結果有如下 2 種。

  • 調用后進入阻塞狀態:互壓量對象已被其他線程獲取,現處于 non-signaled 狀態。
  • 調用后直接返回:其他線程未占用互斥量對象,現處于 signaled 狀態。

互斥量在 WaitForSingleObject 函數返回時自動進入 non-signaled 狀態,因為它是第 19 章介紹過的"auto-reset"模式的內核對象。結果,WaitForSingleObject 函數為申請互斥量時調用的函數。因此,基于互斥量的臨界區保護代碼如下:

WaitForsingleobject(hMutex, INFINITE);
// 臨界區的開始
// ......
// 臨界區的結束
ReleaseMutex(hMutex);

WaitForSingleObject 函數使互斥量進入 non-signaled 狀態,限制訪問臨界區,所以相當于臨界區的門禁系統。相反,ReleaseMutex 函數使互斥量重新進入 signaled 狀態,所以相當于臨界區的出口。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>#define NUM_THREAD 50
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDes(void *arg);long long num = 0;
HANDLE hMutex;int main(int argc, char *argv[])
{HANDLE tHandles[NUM_THREAD];int i;hMutex = CreateMutex(NULL, FALSE, NULL);for (i = 0; i < NUM_THREAD; i++){if (i % 2)tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);CloseHandle(hMutex);printf("result: %lld \n", num);system("pause");return 0;
}unsigned WINAPI threadInc(void *arg)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < 50000000; i++)num += 1;ReleaseMutex(hMutex);return 0;
}unsigned WINAPI threadDes(void *arg)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < 50000000; i++)num -= 1;ReleaseMutex(hMutex);return 0;
}

運行結果:

在這里插入圖片描述

基于信號量對象的同步

Windows 中基于信號量對象的同步也與 Linux 下的信號量類似,二者都是利用名為“信號量值”的整數值完成同步的,而且該值都不能小于 0。當然,Windows 的信號量值注冊于內核對象。

下面介紹創建信號量對象的函數,其銷毀同樣是利用 CloseHandle 函數進行的。

#include <windows.h>HANDLE Createsemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,LONG lInitialCount,LONG lMaximumCount,LPCTSTR lpName
);

參數:

  • IpSemaphoreAttributes:安全配置信息,采用默認安全設置時傳遞 NULL。
  • lInitialCount:指定信號量的初始值,應大于 0 小于 lMaximumCount。
  • IMaximumCount:信號量的最大值。該值為 1 時,信號量變為只能表示 0 和 1 的二進制信號量。
  • lpName:用于命名信號量對象,傳遞 NULL 時創建無名的信號量對象。

成功時返回創建的信號量對象的句柄,失敗時返回 NULL。

向 lInitialCount 參數傳遞 0 時,創建 non-signaled 狀態的信號量對象。而向 IMaximumCount 傳入 3 時,信號量最大值為 3,因此可以實現 3 個線程同時訪問臨界區時的同步。

可以利用“信量值為 0 時進入 non-signaled 狀態,大于 0 時進入 signaled 狀態”的特性進行同步。

下面介紹釋放信號量對象的函數:

#include <windows.h>BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,LPLONG lpPreviouscount
);

參數:

  • Semaphore:傳遞需要釋放的信號量對象.
  • IReleaseCount:釋放意味著信號量值的增加,通過該參數可以指定增加的值。超過最大值則不增加,返回 FALSE。
  • IpPreviousCount:用于保存修改之前值的變量地址,不需要時可傳遞 NULL。

成功時返回 TRUE,失敗時返回 FALSE。

信號量對象的值大于 0 時成為 signaled 狀態,為 0 時成為 non-signaled 狀態。因此,調用 WaitForSingleObject 函數時,信號量大于 0 的情況才會返回,返回的同時將信量值減 1。可以通過如下程序結構保護臨界區。

WaitForSingleObject(hSemaphore, INFINITE);
// 臨界區的開始
// ......
// 臨界區的結束
ReleaseSemaphore(hSemaphore, 1, NULL);

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);static HANDLE sem_one;
static HANDLE sem_two;
static int num;int main(int argc, char const *argv[])
{HANDLE hThread1, hThread2;sem_one = CreateSemaphore(NULL, 0, 1, NULL);sem_two = CreateSemaphore(NULL, 1, 1, NULL);hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(sem_one);CloseHandle(sem_two);system("pause");return 0;
}unsigned WINAPI read(void *arg)
{int i;for (i = 0; i < 5; i++){fputs("Input num: ", stdout);WaitForSingleObject(sem_two, INFINITE);scanf("%d", &num);ReleaseSemaphore(sem_one, 1, NULL);}return 0;
}unsigned WINAPI accu(void *arg)
{int sum = 0, i;for (i = 0; i < 5; i++){WaitForSingleObject(sem_one, INFINITE);sum += num;ReleaseSemaphore(sem_two, 1, NULL);}printf("Result: %d \n", sum);return 0;
}

運行結果:

在這里插入圖片描述

在循環內部構件臨界區,起到盡可能縮小臨界區的作用,盡量提高程序性能。

基于事件對象的同步

事件同步對象與前 2 種同步方法相比有很大不同,區別就在于,該方式下創建對象時,在自動以 non-signaled 狀態運行的“auto-reset”模式和與之相反的“manual-reset”模式中任選其一。而事件對象的主要特點是可以創建“manual-reset”模式的對象。

首先介紹用于創建事件對象的函數:

#include <windows.h>HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName
);

參數:

  • IpEventAttributes:安全配置相關參塑,采用默認安全配置時傳入 NULL。
  • bManualReset:傳入 TRUE 時創建“manual-reset”模式的事件對象,傳入 FALSE 時創建“auto-reset”模式的事件對象。
  • bInitialState:傳入 TRUE 時創建 signaled 狀態的事件對象,傳入 FALSE 時創建 non-signaled態的事件對象。
  • IpName:用于命名事件對象。傳遞 NULL 時創建無名的事件對象。

成功時返回創建的事件對象句柄,失敗時返回 NULL。

相信各位也發現了,上述函數中需要重點關注的是第二個參數。傳人 TRUE 時創建“manual-reset”模式的事件對象,此時即使 WaitForSingleObject 函數返回也不會回到 non-signaled 狀態。

因此,在這種情況下,需要通過如下 2 個函數明確更改對象狀態:

#include <windows.h>BOOL ResetEvent(HANDLE hEvent); // 設置為 non-signaled 狀態
BOOL SetEvent(HANDLE hEvent);   // 設置為 signaled 狀態

成功時返回 TRUE,失敗時返回 FALSE。

傳遞事件對象句柄并希望改為 non-signaled 狀態時,應調用 ResetEvent 函數。如果希望改為 signaled 狀態,則可以調用 SetEvent 函數。

示例程序:

在這里插入代碼片

運行結果:

在這里插入圖片描述

讀入字符串后將事件改成 signaled 狀態,等待的 2 個線程將擺脫等待狀態,開始執行。最后還是把事件對象的狀態改為 non-signaled 狀態。

Windows 平臺下實現多線程服務器端

服務器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>#define BUF_SIZE 100
#define MAX_CLNT 256unsigned WINAPI HandleClnt(void *arg);
void SendMsg(char *msg, int len);
void ErrorHandling(char *msg);int clntCnt = 0;
SOCKET clntSocks[MAX_CLNT];
HANDLE hMutex;int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET hServSock, hClntSock;SOCKADDR_IN servAdr, clntAdr;int clntAdrSz;HANDLE hThread;if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHandling("WSAStartup() error!");hMutex = CreateMutex(NULL, FALSE, NULL);hServSock = socket(PF_INET, SOCK_STREAM, 0);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(atoi(argv[1]));if (bind(hServSock, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)ErrorHandling("bind() error");if (listen(hServSock, 5) == SOCKET_ERROR)ErrorHandling("listen() error");while (1){clntAdrSz = sizeof(clntAdr);hClntSock = accept(hServSock, (SOCKADDR *)&clntAdr, &clntAdrSz);WaitForSingleObject(hMutex, INFINITE);clntSocks[clntCnt++] = hClntSock;ReleaseMutex(hMutex);hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void *)&hClntSock, 0, NULL);printf("Connected client IP: %s \n", inet_ntoa(clntAdr.sin_addr));}closesocket(hServSock);WSACleanup();return 0;
}unsigned WINAPI HandleClnt(void *arg)
{SOCKET hClntSock = *((SOCKET *)arg);int strLen = 0, i;char msg[BUF_SIZE];while ((strLen = recv(hClntSock, msg, sizeof(msg), 0)) != 0)SendMsg(msg, strLen);WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clntCnt; i++){if (hClntSock == clntSocks[i]){while (i++ < clntCnt - 1)clntSocks[i] = clntSocks[i + 1];break;}}clntCnt--;ReleaseMutex(hMutex);closesocket(hClntSock);return 0;
}void SendMsg(char *msg, int len)
{ // 發送給全部人int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clntCnt; i++)send(clntSocks[i], msg, len, 0);ReleaseMutex(hMutex);
}void ErrorHandling(char *msg)
{fputs(msg, stderr);fputc('\n', stderr);exit(1);
}

客戶端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>#define BUF_SIZE 100
#define NAME_SIZE 20unsigned WINAPI SendMsg(void *arg);
unsigned WINAPI RecvMsg(void *arg);
void ErrorHandling(char *msg);char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET hSock;SOCKADDR_IN servAdr;HANDLE hSndThread, hRcvThread;if (argc != 4){printf("Usage: %s <IP> <port> <name>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHandling("WSAStartup() error!");sprintf(name, "[%s]", argv[3]);hSock = socket(PF_INET, SOCK_STREAM, 0);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = inet_addr(argv[1]);servAdr.sin_port = htons(atoi(argv[2]));if (connect(hSock, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)ErrorHandling("connect() error");hSndThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void *)&hSock, 0, NULL);hRcvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void *)&hSock, 0, NULL);WaitForSingleObject(hSndThread, INFINITE);WaitForSingleObject(hRcvThread, INFINITE);closesocket(hSock);WSACleanup();return 0;
}unsigned WINAPI SendMsg(void *arg)
{SOCKET hSock = *((SOCKET *)arg);char nameMsg[NAME_SIZE + BUF_SIZE];while (1){fgets(msg, BUF_SIZE, stdin);if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")){closesocket(hSock);exit(0);}sprintf(nameMsg, "%s %s", name, msg);send(hSock, nameMsg, strlen(nameMsg), 0);}return 0;
}unsigned WINAPI RecvMsg(void *arg)
{int hSock = *((SOCKET *)arg);char nameMsg[NAME_SIZE + BUF_SIZE];int strLen;while (1){strLen = recv(hSock, nameMsg, NAME_SIZE + BUF_SIZE - 1, 0);if (strLen == -1)return -1;nameMsg[strLen] = '\0';fputs(nameMsg, stdout);}return 0;
}void ErrorHandling(char *msg)
{fputs(msg, stderr);fputc('\n', stderr);exit(1);
}

編譯:

gcc chat_server_win.c -lwsock32 -o cserv
gcc chat_client_win.c -lwsock32 -o cclnt

運行結果:

在這里插入圖片描述

習題

(1)關于 Windows 操作系統的用戶模式和內核模式的說法正確的是?

a. 用戶模式是應用程序運行的基本模式,雖然訪問的內存空間沒有限制,但無法訪問物理設備。
b. 應用程序運行過程中絕對不會進人內核模式。應用程序只在用戶模式中運行。
c. Windows 為了有效使用內存空間,分別定義了用戶模式和內核模式。
d. 應用程序運行過程中也有可能切換到內核模式。只是切換到內核模式后,進程將一直保持該狀態。

答:

c。

(2)判斷下列關于用戶模式同步和內核模式同步描述的正誤。

  • 用戶模式的同步中不會切換到內核模式。即非操作系統級別的同步。( √ )
  • 內核模式的同步是由操作系統提供的功能,比用戶模式同步提供更多功能。( √ )
  • 需要在用戶模式和內核模式之間切換,這是內核模式同步的缺點。( √ )
  • 除特殊情況外,原則上應使用內核模式同步。用戶模式同步是操作系統提供內核模式同步機制前使用的同步方法。( × )

(3)本章示例SyncSema_win.c 的 Read 函數中,退出臨界區需要較長時間,請給出解決方案并實現。

用一個數組存儲輸入的 5 個數字,將整個循環改為臨界區,減少進入/離開臨界區的次數。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);static HANDLE sem_one, sem_two;
static int arr[5];int main(int argc, char const *argv[])
{HANDLE hThread1, hThread2;sem_one = CreateSemaphore(NULL, 0, 1, NULL);sem_two = CreateSemaphore(NULL, 1, 1, NULL);hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(sem_one);CloseHandle(sem_two);system("pause");return 0;
}unsigned WINAPI read(void *arg)
{WaitForSingleObject(sem_two, INFINITE);for (int i = 0; i < 5; i++){fputs("Input num: ", stdout);scanf("%d", &arr[i]);}ReleaseSemaphore(sem_one, 1, NULL);return 0;
}unsigned WINAPI accu(void *arg)
{int sum = 0;WaitForSingleObject(sem_one, INFINITE);for (int i = 0; i < 5; i++)sum += arr[i];ReleaseSemaphore(sem_two, 1, NULL);printf("Result: %d \n", sum);return 0;
}

運行結果:

在這里插入圖片描述

(4)請將本章 SyncEvent_win.c 示例改為基于信號量的同步方式,并得出相同運行結果。

將 hEvent 替換為 hSemaphore,使用 CreateSemaphore 函數創建初始計數為 0 的信號量。

主線程在輸入完成后調用 ReleaseSemaphore(hSemaphore, 2, NULL),一次性釋放 2 個信號量值,確保兩個線程都能繼續執行。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>#define STR_LEN 100unsigned WINAPI NumberOfA(void *arg);
unsigned WINAPI NumberOfOthers(void *arg);static HANDLE hSemaphore;
static char str[STR_LEN];int main(int argc, char const *argv[])
{HANDLE hThread1, hThread2;// 創建信號量,初始計數 0,最大計數 2hSemaphore = CreateSemaphore(NULL, 0, 2, NULL);hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);fputs("Input string: ", stdout);fgets(str, STR_LEN, stdin);// 釋放 2 個信號量值,允許兩個線程繼續執行ReleaseSemaphore(hSemaphore, 2, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(hSemaphore);system("pause");return 0;
}unsigned WINAPI NumberOfA(void *arg)
{int i, cnt = 0;WaitForSingleObject(hSemaphore, INFINITE); // 等待信號量for (i = 0; str[i] != '\0'; i++){if (str[i] == 'A')cnt++;}printf("Num of A: %d\n", cnt);return 0;
}unsigned WINAPI NumberOfOthers(void *arg)
{int i, cnt = 0;WaitForSingleObject(hSemaphore, INFINITE); // 等待信號量for (i = 0; str[i] != '\0'; i++){if (str[i] != 'A')cnt++;}printf("Num of others: %d\n", cnt - 1); // 減去換行符return 0;
}

運行結果:

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73088.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73088.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73088.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VBA-Excel

VBA 一、數據類型與變量 常用數據類型&#xff1a; Byte&#xff1a;字節型&#xff0c;0~255。Integer&#xff1a;整數型&#xff0c;用于存儲整數值&#xff0c;范圍 -32768 到 32767。Long&#xff1a;長整型&#xff0c;可存儲更大范圍的整數&#xff0c;范圍 -214748364…

kotlin 內聯函數 inline

高階函數實現的原理&#xff1a;函數類型其實是生成了一個對象 。 inline翻譯成中文的意思就是內聯&#xff0c;在kotlin里面inline被用來修飾函數&#xff0c;表明當前函數在編譯時是以內嵌的形式進行編譯的&#xff0c;從而減少了一層函數調用棧&#xff1a; inline fun fun…

PairRE: Knowledge Graph Embeddings via Paired Relation Vectors(論文筆記)

CCF等級&#xff1a;A 發布時間&#xff1a;2020年11月 25年3月24日交 目錄 一、簡介 二、原理 1.整體 2.關系模式 3.優化模型 三、實驗性能 四、結論和未來工作 一、簡介 將RotatE進行生級&#xff0c;RotatE只對頭實體h進行計算&#xff0c;PairRE對頭尾實體都進行…

從報錯到成功:Mermaid 流程圖語法避坑指南?

&#x1f680; 從報錯到成功&#xff1a;Mermaid 流程圖語法避坑指南 &#x1f680; &#x1f6a8; 問題背景 在開發文檔或技術博客中&#xff0c;我們經常使用 Mermaid 流程圖 來可視化代碼邏輯。但最近我在嘗試繪制一個 Java Stream 轉換流程圖時&#xff0c;遭遇了以下報錯…

深入解析 Redis 實現分布式鎖的最佳實踐

前言 在分布式系統中&#xff0c;多個進程或線程可能會同時訪問同一個共享資源&#xff0c;這就可能導致數據不一致的問題。為了保證數據的一致性&#xff0c;我們通常需要使用分布式鎖。Redis 作為高性能的內存數據庫&#xff0c;提供了一種簡單高效的方式來實現分布式鎖。本…

2025年03月10日人慧前端面試(外包滴滴)

目錄 普通函數和箭頭函數的區別loader 和 plugin 的區別webpack 怎么實現分包&#xff0c;為什么要分包webpack 的構建流程變量提升react 開發中遇到過什么問題什么是閉包vue 開發中遇到過什么問題vue中的 dep 和 watcher 的依賴收集是什么階段什么是原型鏈react setState 是同…

Android10 系統截屏功能異常的處理

客戶反饋的問題&#xff0c;設備上使用狀態欄中“長截屏”功能&#xff0c;截屏失敗且出現系統卡死問題。 在此記錄該問題的處理 一現象&#xff1a; 設備A10上使用系統“長截屏”功能&#xff0c;出現截屏失敗&#xff0c;系統死機。 二復現問題并分析 使用設備操作該功能&…

openvela新時代的國產開源RTOS系統

openvela 簡介 openvela 操作系統專為 AIoT 領域量身定制&#xff0c;以輕量化、標準兼容、安全性和高度可擴展性為核心特點。openvela 以其卓越的技術優勢&#xff0c;已成為眾多物聯網設備和 AI 硬件的技術首選&#xff0c;涵蓋了智能手表、運動手環、智能音箱、耳機、智能家…

ENSP學習day9

ACL訪問控制列表實驗 ACL&#xff08;Access Control List&#xff0c;訪問控制列表&#xff09;是一種用于控制用戶或系統對資源&#xff08;如文件、文件夾、網絡等&#xff09;訪問權限的機制。通過ACL&#xff0c;系統管理員可以定義哪些用戶或系統可以訪問特定資源&#x…

JVM的組成--運行時數據區

JVM的組成 1、類加載器&#xff08;ClassLoader&#xff09; 類加載器負責將字節碼文件從文件系統中加載到JVM中&#xff0c;分為&#xff1a;加載、鏈接&#xff08;驗證、準備、解析&#xff09;、和初始化三個階段 2、運行時數據區 運行時數據區包括&#xff1a;程序計數…

RAG(Retrieval-Augmented Generation)基建之PDF解析的“魔法”與“陷阱”

嘿&#xff0c;親愛的算法工程師們&#xff01;今天咱們聊一聊PDF解析的那些事兒&#xff0c;簡直就像是在玩一場“信息捉迷藏”游戲&#xff01;PDF文檔就像是個調皮的小精靈&#xff0c;表面上看起來規規矩矩&#xff0c;但當你想要從它那里提取信息時&#xff0c;它就開始跟…

Python網絡編程入門

一.Socket 簡稱套接字&#xff0c;是進程之間通信的一個工具&#xff0c;好比現實生活中的插座&#xff0c;所有的家用電器要想工作都是基于插座進行&#xff0c;進程之間要想進行網絡通信需要Socket&#xff0c;Socket好比數據的搬運工~ 2個進程之間通過Socket進行相互通訊&a…

人工智能(AI)系統化學習路線

一、為什么需要系統化學習AI&#xff1f; 人工智能技術正在重塑各行各業&#xff0c;但許多初學者容易陷入誤區&#xff1a; ? 盲目跟風&#xff1a;直接學習TensorFlow/PyTorch&#xff0c;忽視數學與算法基礎。 ? 紙上談兵&#xff1a;只看理論不寫代碼&#xff0c;無法解…

mac calDAV 日歷交互

安裝Bakal docker https://sabre.io/dav/building-a-caldav-client/ 在Bakal服務器上注冊賬戶 http://localhost:8080/admin/?/users/calendars/user/1/ 在日歷端登錄賬戶&#xff1a; Server: http://127.0.0.1:8080/dav.php Server Path: /dav.php/principals/lion No e…

手機號登錄與高并發思考

基礎邏輯 一般來說這個驗證碼登錄分為手機號、以及郵箱登錄 手機號短信驗證&#xff0c;以騰訊云SMS 服務為例&#xff1a; 這個操作無非對后端來說就是兩個接口&#xff1a; 一個是獲取驗證碼&#xff0c;這塊后端生成6位數字expire_time 去推送到騰訊云sdk &#xff0c;騰…

Python設計模式 - 適配器模式

定義 適配器模式&#xff08;Adapter Pattern&#xff09;是一種結構型設計模式&#xff0c;它用于將一個類的接口轉換為客戶端所期待的另一個接口。 注&#xff1a;在適配器模式定義中所提及的接口是指廣義的接口&#xff0c;它可以表示一個方法或者一組方法的集合。 結構 …

【前端工程化】

目錄 前端工程戶核心技術之模塊化前端模塊化的進化過程commonjs規范介紹commonjs規范示例commonjs模塊打包 amd規范、cmd規范前端工程化關鍵技術之npmwebpack原理 前端工程戶核心技術之模塊化 前端模塊化是一種標準&#xff0c;不是實現。commonjs是前端模塊化的標準&#xff…

關于CNN,RNN,GAN,GNN,DQN,Transformer,LSTM,DBN你了解多少

以下是神經網絡中常見的幾種模型的簡要介紹&#xff1a; 1. ?CNN (Convolutional Neural Network, 卷積神經網絡) ?用途: 主要用于圖像處理和計算機視覺任務。?特點: 通過卷積核提取局部特征&#xff0c;具有平移不變性&#xff0c;能夠有效處理高維數據&#xff08;如圖像…

T113-i開發板的休眠與RTC定時喚醒指南

??在嵌入式系統設計中&#xff0c;休眠與喚醒技術是優化電源管理、延長設備續航的關鍵。飛凌嵌入式基于全志T113-i處理器開發設計的OK113i-S開發板提供了兩種休眠模式&#xff1a;freeze和mem&#xff0c;以滿足不同應用場景下的功耗與恢復速度需求。本文將詳細介紹如何讓OK1…

SpringBoot項目實戰(初級)

目錄 一、數據庫搭建 二、代碼開發 1.pom.xml 2.thymeleaf模塊處理的配置類 3.application配置文件 4.配置&#xff08;在啟動類中&#xff09; 5.編寫數據層 ②編寫dao層 ③編寫service層 接口 實現類 注意 補充&#xff08;注入的3個注解&#xff09; 1.AutoWir…