建立程序,設計訊號量同步機制,實現多執行緒同步 - C語言版

尘尘尘發表於2024-10-09
  • 環境:Windows11
  • 編譯器:Visual Studio 2019

相關標頭檔案:

#include <windows.h>
#include <stdio.h>

相關函式:

  1. 睡眠等待函式:Sleep(int millisecond);
    睡眠等待一定時間,會造成OS重新排程其它的執行緒執行
Sleep(10);   //當前執行緒睡眠10毫秒後重新執行
  1. 建立程序
CreateProcess(
  LPSECURITY_ATTRIBUTES // 是否繼承程序控制代碼
  LPSECURITY_ATTRIBUTES //是否繼承執行緒控制代碼
  BOOL bInheritHandles //是否繼承控制代碼
  DWORD dwCreationFlags //有沒有建立標誌
  LPVOID lpEnvironment // 是否使用父程序環境變數
  LPCTSTR lpCurrentDirectory //使用父程序目錄作為當前目錄,可以自己設定目錄
  LPSTARTUPINFO lpStartupInfo //STARTUPINFOW結構體詳細資訊(啟動狀態相關資訊)
  LPPROCESS_INFORMATION //PROCESS_INFORMATION結構體程序資訊
);
  1. 啟動執行緒:CreateThread(ThreadAttribures, stack_size, ThreadFunctionAddress, Parameters, CreationFlags, ThreadID);
HANDLE t1 = CreateThread(NULL,0,Func,NULL,0,&ThreadID);
  1. 定義訊號量:Semaphore
HANDLE sema;
  1. 建立訊號量:CreateSemaphore(Attributes,InitialCount, MaxCount, SemaphoreID);
sema = CreateSemaphore(NULL, 0, 1, NULL);
  1. 申請訪問訊號量:WaitForSingleObject(HANDLE, millisecond);
    呼叫該函式後,如果訊號量為0,則執行緒阻塞;否則訊號量當前值減1,然後繼續執行下一行語句;
WaitForSingleObject(sema,INFINITE);
  1. 釋放訊號量:ReleaseSemaphore(HANDLE, releaseCount, *PreviousCount);
    呼叫該函式後,訊號量當前值加上releastCount,然後繼續執行下一行語句;
ReleaseSemaphore(sema,1,NULL);    //訊號量加1

--- 1. 在程式中根據使用者輸入的可執行程式名稱,建立一個程序來執行該可執行程式。 ```c #define _CRT_SECURE_NO_WARNINGS #include #include

int main() {
// 定義和初始化STARTUPINFO 和 PROCESS_INFROMATION結構體
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si)); // 將 si 結構體的記憶體置零,確保其中沒有任何殘留資料
si.cb = sizeof(si); // 表示結構體的大小
ZeroMemory(&pi, sizeof(pi));

char name[100]; // 接收檔名
char path[100]; // 完整路徑
wchar_t wpath[200]; // 寬字元路徑

printf("Please input program to run: ");
scanf("%99s", name);

// 將基礎路徑複製到 path 中
strcpy(path, "C:\\Windows\\System32\\");

// 拼接檔名到路徑
strcat(path, name);

mbstowcs(wpath, path, sizeof(wpath) / sizeof(wchar_t));

// 建立程序
if (!CreateProcess(
	wpath,  // 檔案路徑
	NULL, // 命令列引數
	NULL, // 預設安全屬性
	NULL, // 預設安全屬性
	FALSE, // 不繼承控制代碼
	0, // 預設建立標誌
	NULL, // 使用父程序的環境變數
	NULL, // 使用父程序的當前目錄
	&si, // 啟動資訊
	&pi) // 程序資訊
	) {
	printf("建立程序失敗\n");
	return -1;
}

// 等待程序結束
WaitForSingleObject(pi.hProcess, INFINITE);
// 關閉程序和執行緒控制代碼
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return 0;

}

<br>
> **個人問題記錄:**

起初測試CreateProcess函式是否正確傳參時發現該函式不相容char *型別
![](https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241009001909402-1354116522.png)

- 在 Windows API 中,使用 Unicode 字串時,需要使用 L 字首來定義寬字元字串:
![](https://img2024.cnblogs.com/blog/3531058/202410/3531058-20241009001934208-364686058.png)
<br>
2. 假設有四個執行緒,第一個執行緒輸出字串 “This”,第二個執行緒輸出字串 “is”, 第三個執行緒輸出字串“Jinan”, 第四個執行緒輸出字串 “University!”。編制C/C++程式,在主程式main函式中建立四個執行緒並依次啟動,設計訊號量(Semaphore)同步機制,當主程式執行時,螢幕輸出的結果是字串“This is Jinan University!” 
```c
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>

HANDLE sema;  // 定義訊號量
const char* words[] = { "This", "is", "Jinan", "University!" };

DWORD WINAPI ThreadFunction(LPVOID lpParam) {
	// 申請訪問訊號量
	WaitForSingleObject(sema, INFINITE);

	int threadNum = *((int*)lpParam);
	printf("%s ", words[threadNum - 1]);

	// 釋放訊號量
	ReleaseSemaphore(sema, 1, NULL);

	return 0;
}
int main() {
	HANDLE threads[4]; // 儲存執行緒控制代碼的陣列
	DWORD threadID;

	// 建立訊號量,初始計數為0,最大計數為1
	sema = CreateSemaphore(NULL, 0, 1, NULL);
	if (sema == NULL) {
		printf("建立訊號量失敗\n");
		return 1;
	}
	// 建立並啟動執行緒
	for (int i = 0; i < 4; i++) {
		int* pNum = (int*)malloc(sizeof(int)); // 為每個執行緒分配記憶體
		*pNum = i + 1; // 設定執行緒編號

		threads[i] = CreateThread(
			NULL,               // 預設安全屬性
			0,                  // 預設堆疊大小
			ThreadFunction,     // 執行緒函式的地址
			pNum,                 // 傳遞執行緒編號
			0,                  // 預設建立標誌
			&threadID           // 執行緒識別符號
		);

		if (threads[i] == NULL) {
			printf("執行緒 %d 啟動失敗\n", i + 1);
			return 1;
		}

		// 釋放訊號量
		Sleep(100);
		ReleaseSemaphore(sema, 1, NULL);
	}

	// 等待所有執行緒完成
	WaitForMultipleObjects(4, threads, TRUE, INFINITE);

	// 關閉執行緒控制代碼
	for (int i = 0; i < 4; i++) {
		CloseHandle(threads[i]);
	}

	// 關閉訊號量控制代碼
	CloseHandle(sema);

	return 0;
}

個人問題記錄:

輸出threadNum時,發現for迴圈裡每個輸出的threadNum都是4

#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>

HANDLE sema;  // 定義訊號量

DWORD WINAPI ThreadFunction(LPVOID lpParam) {

	// 申請訪問訊號量
	WaitForSingleObject(sema, INFINITE);

	int threadNum = *((int*)lpParam);
	printf("%d\n", threadNum);

	// 釋放訊號量
	ReleaseSemaphore(sema, 1, NULL);

	return 0;
}
int main() {
	HANDLE threads[4]; // 儲存執行緒控制代碼的陣列
	DWORD threadID;

	// 建立訊號量,初始計數為0,最大計數為1
	sema = CreateSemaphore(NULL, 0, 1, NULL);
	if (sema == NULL) {
		printf("建立訊號量失敗\n");
		return 1;
	}

	// 建立並啟動執行緒
	for (int i = 0; i < 4; i++) {
		threads[i] = CreateThread(
			NULL,               // 預設安全屬性
			0,                  // 預設堆疊大小
			ThreadFunction,     // 執行緒函式的地址
			&i,                 // 傳遞執行緒編號
			0,                  // 預設建立標誌
			&threadID           // 執行緒識別符號
		);

		if (threads[i] == NULL) {
			printf("執行緒 %d 啟動失敗\n", i + 1);
			return 1;
		}

		// 釋放訊號量
		ReleaseSemaphore(sema, 1, NULL);
	}

	// 等待所有執行緒完成
	WaitForMultipleObjects(4, threads, TRUE, INFINITE);

	// 關閉執行緒控制代碼
	for (int i = 0; i < 4; i++) {
		CloseHandle(threads[i]);
	}

	// 關閉訊號量控制代碼
	CloseHandle(sema);
	return 0;
}

  • 因為傳遞給執行緒函式的引數是變數 i 的地址,而在迴圈中 i 是遞增的。所有執行緒都在共享同一個 i 的地址,所以當所有執行緒執行時,它們都看到的是 i 的最終值——即迴圈結束後 i 的值是 4
  • 應該為每個執行緒分配記憶體:int* pNum = (int*)malloc(sizeof(int));



  1. 基於實驗題目2,在主函式中依次啟動四個執行緒,修改主程式,使得給定使用者任意輸入的整數n,程式輸出n個同樣的字串“This is Jinan University!”

在題目2的基礎上套一層for迴圈即可

#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>

HANDLE sema;  // 定義訊號量
const char* words[] = { "This", "is", "Jinan", "University!" };

DWORD WINAPI ThreadFunction(LPVOID lpParam) {

	// 申請訪問訊號量
	WaitForSingleObject(sema, INFINITE);

	int threadNum = *((int*)lpParam);
	printf("%s ", words[threadNum - 1]);

	// 釋放訊號量
	ReleaseSemaphore(sema, 1, NULL);

	return 0;
}

int main() {
	int n;
	printf("n = ");
	scanf("%d", &n);
	for (int k = 0; k < n; k++) {
		HANDLE threads[4]; // 儲存執行緒控制代碼的陣列
		DWORD threadID;

		// 建立訊號量,初始計數為0,最大計數為1
		sema = CreateSemaphore(NULL, 0, 1, NULL);
		if (sema == NULL) {
			printf("建立訊號量失敗\n");
			return 1;
		}
		// 建立並啟動執行緒
		for (int i = 0; i < 4; i++) {
			int* pNum = (int*)malloc(sizeof(int)); // 為每個執行緒分配記憶體
			*pNum = i + 1; // 設定執行緒編號

			threads[i] = CreateThread(
				NULL,               // 預設安全屬性
				0,                  // 預設堆疊大小
				ThreadFunction,     // 執行緒函式的地址
				pNum,                 // 傳遞執行緒編號
				0,                  // 預設建立標誌
				&threadID           // 執行緒識別符號
			);

			if (threads[i] == NULL) {
				printf("執行緒 %d 啟動失敗\n", i + 1);
				return 1;
			}

			// 釋放訊號量
			Sleep(100);
			ReleaseSemaphore(sema, 1, NULL);
		}

		// 等待所有執行緒完成
		WaitForMultipleObjects(4, threads, TRUE, INFINITE);

		// 關閉執行緒控制代碼
		for (int i = 0; i < 4; i++) {
			CloseHandle(threads[i]);
		}

		// 關閉訊號量控制代碼
		CloseHandle(sema);
		printf("\n");
	}
	return 0;
}

相關文章