Windows平台C语言多线程
1、windows中的示例代码
// sample_multithread_c_program.c
// compile with: /c
//
// Bounce - Creates a new thread each time the letter 'a' is typed.
// Each thread bounces a character of a different color around
// the screen. All threads are terminated when the letter 'Q' is
// entered.
//
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <process.h>
#define MAX_THREADS 32
// The function getrandom returns a random number between
// min and max, which must be in integer range.
#define getrandom( min, max ) (SHORT)((rand() % (int)(((max) + 1) - \
(min))) + (min))
int main(void); // Thread 1: main
void KbdFunc(void); // Keyboard input, thread dispatch
void BounceProc(void* pMyID); // Threads 2 to n: display
void ClearScreen(void); // Screen clear
void ShutDown(void); // Program shutdown
void WriteTitle(int ThreadNum); // Display title bar information
HANDLE hConsoleOut; // Handle to the console
HANDLE hRunMutex; // "Keep Running" mutex
HANDLE hScreenMutex; // "Screen update" mutex
int ThreadNr = 0; // Number of threads started
CONSOLE_SCREEN_BUFFER_INFO csbiInfo; // Console information
COORD consoleSize;
BOOL bTrails = FALSE;
HANDLE hThreads[MAX_THREADS] = { NULL }; // Handles for created threads
int main(void) // Thread One
{
// Get display screen information & clear the screen.
hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hConsoleOut, &csbiInfo);
consoleSize.X = csbiInfo.srWindow.Right;
consoleSize.Y = csbiInfo.srWindow.Bottom;
ClearScreen();
WriteTitle(0);
// Create the mutexes and reset thread count.
hScreenMutex = CreateMutexW(NULL, FALSE, NULL); // Cleared
hRunMutex = CreateMutexW(NULL, TRUE, NULL); // Set
// Start waiting for keyboard input to dispatch threads or exit.
KbdFunc();
// All threads done. Clean up handles.
if (hScreenMutex) CloseHandle(hScreenMutex);
if (hRunMutex) CloseHandle(hRunMutex);
if (hConsoleOut) CloseHandle(hConsoleOut);
}
void ShutDown(void) // Shut down threads
{
// Tell all threads to die
ReleaseMutex(hRunMutex);
while (ThreadNr > 0)
{
// Wait for each thread to complete
WaitForSingleObject(hThreads[--ThreadNr], INFINITE);
}
// Clean up display when done
WaitForSingleObject(hScreenMutex, INFINITE);
ClearScreen();
}
void KbdFunc(void) // Dispatch and count threads.
{
int KeyInfo;
do
{
KeyInfo = _getch();
if (tolower(KeyInfo) == 'a' &&
ThreadNr < MAX_THREADS)
{
++ThreadNr;
hThreads[ThreadNr] =
(HANDLE)_beginthread(BounceProc, 0, (void*)(uintptr_t)ThreadNr);
WriteTitle(ThreadNr);
}
if (tolower(KeyInfo) == 't')
{
bTrails = !bTrails;
}
} while (tolower(KeyInfo) != 'q');
ShutDown();
}
void BounceProc(void* pMyID)
{
wchar_t MyCell, OldCell;
WORD MyAttrib, OldAttrib = 0;
wchar_t BlankCell = 0x20;
COORD Coords, Delta;
COORD Old = { 0,0 };
DWORD Dummy;
int MyID = (int)(uintptr_t)pMyID;
// Generate update increments and initial
// display coordinates.
srand(MyID * 3);
Coords.X = getrandom(0, consoleSize.X - 1);
Coords.Y = getrandom(0, consoleSize.Y - 1);
Delta.X = getrandom(-3, 3);
Delta.Y = getrandom(-3, 3);
// Set up character & generate color
// attribute from thread number.
if (MyID > 16)
MyCell = (wchar_t)(0x60 + MyID - 16); // lower case
else
MyCell = (wchar_t)(0x40 + MyID); // upper case
MyAttrib = MyID & 0x0f; // force black background
do
{
// Wait for display to be available, then lock it.
WaitForSingleObject(hScreenMutex, INFINITE);
if (!bTrails)
{
// If we still occupy the old screen position, blank it out.
ReadConsoleOutputCharacterW(hConsoleOut, &OldCell, 1,
Old, &Dummy);
ReadConsoleOutputAttribute(hConsoleOut, &OldAttrib, 1,
Old, &Dummy);
if ((OldCell == MyCell) && (OldAttrib == MyAttrib))
WriteConsoleOutputCharacterW(hConsoleOut, &BlankCell, 1,
Old, &Dummy);
}
// Draw new character, then clear screen lock
WriteConsoleOutputCharacterW(hConsoleOut, &MyCell, 1,
Coords, &Dummy);
WriteConsoleOutputAttribute(hConsoleOut, &MyAttrib, 1,
Coords, &Dummy);
ReleaseMutex(hScreenMutex);
// Increment the coordinates for next placement of the block.
Old.X = Coords.X;
Old.Y = Coords.Y;
Coords.X += Delta.X;
Coords.Y += Delta.Y;
// If we are about to go off the screen, reverse direction
if (Coords.X < 0 || Coords.X >= consoleSize.X)
{
Delta.X = -Delta.X;
Beep(400, 50);
}
if (Coords.Y < 0 || Coords.Y > consoleSize.Y)
{
Delta.Y = -Delta.Y;
Beep(600, 50);
}
}
// Repeat while RunMutex is still taken.
while (WaitForSingleObject(hRunMutex, 75L) == WAIT_TIMEOUT);
}
void WriteTitle(int ThreadNum)
{
enum
{
sizeOfNThreadMsg = 120
};
wchar_t NThreadMsg[sizeOfNThreadMsg] = { L"" };
swprintf_s(NThreadMsg, sizeOfNThreadMsg,
L"Threads running: %02d. Press 'A' "
L"to start a thread, 'T' to toggle "
L"trails, 'Q' to quit.", ThreadNum);
SetConsoleTitleW(NThreadMsg);
}
void ClearScreen(void)
{
DWORD dummy = 0;
COORD Home = { 0, 0 };
FillConsoleOutputCharacterW(hConsoleOut, L' ',
consoleSize.X * consoleSize.Y,
Home, &dummy);
}
2、windows平台多线程API
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define NUM_THREADS 4
#define ARRAY_SIZE 1000
int array[ARRAY_SIZE];
int max_value = 0;
HANDLE max_value_mutex; // 定义互斥句柄
DWORD WINAPI find_max(LPVOID lpParam) {
long tid = (long)lpParam;
int local_max = 0;
int start = tid * (ARRAY_SIZE / NUM_THREADS); // 每个线程处理的起始索引
int end = (tid + 1) * (ARRAY_SIZE / NUM_THREADS); // 线程处理的最终索引
for (int i = start; i < end; i++) {
if (array[i] > local_max) {
local_max = array[i];
}
}
WaitForSingleObject(max_value_mutex, INFINITE); // 获取到互斥锁的信号并无限等待,知道互斥锁信号为signaled,调用时会被阻塞,知道等待到互斥锁的信号
if (local_max > max_value) {
max_value = local_max;
}
ReleaseMutex(max_value_mutex); // 释放全局变量,允许其他资源访问
return 0;
}
int main() {
HANDLE threads[NUM_THREADS]; // 定义线程数
DWORD rc;
long t;
max_value_mutex = CreateMutex(NULL, FALSE, NULL);
for (int i = 0; i < ARRAY_SIZE; i++) {
array[i] = rand() % 1000;
}
for (t = 0; t < NUM_THREADS; t++) {
threads[t] = CreateThread(NULL, 0, find_max, (LPVOID)t, 0, NULL); // 创建线程,返回句柄操作该线程
if (threads[t] == NULL) {
printf("ERROR: Unable to create thread\n");
ExitProcess(-1);
}
}
WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE);
printf("The maximum value in the array is: %d\n", max_value);
CloseHandle(max_value_mutex); // 关闭句柄
ExitProcess(0);
}
/* 注释1 */
// DWORD 是 Windows API 中定义的一种数据类型,它表示双字(Double Word),即一个 32 位的无符号整数。WINAPI 是一个宏,用于指定函数调用约定,表示该函数使用的是标准的 Windows API 调用约定。
// DWORD WINAPI 是在 Windows 环境下声明函数的一种约定,它告诉编译器和链接器这个函数应该使用的是 Windows API 的调用约定,并且返回一个 DWORD 类型的值。
// WINAPI 宏会根据不同的编译环境做不同的处理。在 32 位编译环境下,它会被定义为空,而在 64 位编译环境下,它会被定义为 __stdcall。__stdcall 是一种标准的函数调用约定,它会将参数按照从右向左的顺序入栈,由被调用的函数负责清理栈空间。
// 综上所述,DWORD WINAPI 表示函数返回一个 32 位的无符号整数,并且使用 Windows API 的调用约定。这种约定适用于在 Windows 环境下编写使用 Windows API 的函数
/* 注释2 */
// LPVOID 是一个指向 void 类型的指针。在 Windows API 中经常使用 LP 前缀来表示长指针(Long Pointer),但在实际上它与普通的指针类型没有本质区别。
// lpParam 是一个函数的参数,它被声明为 LPVOID 类型,表示可以接受任意类型的指针。在 Windows API 中,通常使用 LPVOID 类型来传递任意类型的参数给线程函数。
// 在多线程编程中,你可能会希望在线程创建时传递一些额外的参数给线程函数,而这些参数的类型可以是任意的。通过将参数声明为 LPVOID 类型,你可以将任意类型的指针转换为 LPVOID 类型,然后在线程函数中将其转换回原来的类型,以获取传递给线程的实际参数。
// 例如,在 Windows 线程函数 CreateThread 中,你可以将 lpParam 参数用于传递给线程函数的参数,然后在线程函数内部进行类型转换,以获取原始的参数。
/* 注释3:start 和 end */
// 在多线程环境下,start 和 end 通常用于确定每个线程需要处理的数据范围。这种情况通常发生在将数据划分为多个部分,每个线程处理其中一部分的情况下。
// 在示例中,start 和 end 表示每个线程需要处理的数组索引范围。通过计算出每个线程负责处理的部分,可以有效地将任务分配给多个线程,从而提高程序的并发性能。
// 具体而言,start 表示当前线程需要处理的起始索引,而 end 表示当前线程需要处理的结束索引(不包含)。例如,如果 start 是 0,end 是 100,那么当前线程将处理数组的索引从 0 到 99 的元素。
// 通过这种方式,每个线程都可以并行处理数组的不同部分,从而提高整体处理速度。
/* 注释3:多线程中句柄 */
// 在 Windows 环境下,HANDLE 是一种句柄类型,它可以用来表示各种资源,包括文件、事件、互斥体、信号量等。在这里,max_value_mutex 是一个互斥体的句柄,用于实现多线程之间的互斥访问,确保对共享资源的安全访问。
// 互斥体(Mutex)是一种同步对象,用于实现多线程之间的互斥访问。它提供了两个主要的操作:锁定(lock)和解锁(unlock)。当一个线程获得了互斥体的锁,其他线程就不能再访问受保护的资源,直到锁被释放。
// 在示例代码中,max_value_mutex 被用来保护 max_value 全局变量的访问,以确保多个线程在更新 max_value 变量时不会发生竞争条件。每个线程在更新 max_value 之前,首先需要获取 max_value_mutex 的锁,在更新完成后释放锁。
// 通过使用互斥体,可以确保在任意时刻只有一个线程能够访问 max_value 变量,从而避免了多线程之间的竞争条件和数据不一致性问题。
/* 注释3:同步函数 */
// WaitForSingleObject 函数是 Windows API 中的一个同步函数,用于等待一个对象的状态发生变化。在这里,WaitForSingleObject 用于等待互斥体对象的信号状态。max_value_mutex 是一个互斥体的句柄,表示等待的对象。
// WaitForSingleObject 函数的第二个参数指定了等待时间,这里使用了 INFINITE 宏,表示无限等待,即直到互斥体对象的状态发生变化。
// 在多线程编程中,WaitForSingleObject 通常与互斥体一起使用,用于实现线程之间的同步。在这个例子中,当线程调用 WaitForSingleObject(max_value_mutex, INFINITE) 时,它会被阻塞,直到获取了 max_value_mutex 的锁,也就是直到互斥体对象的状态变为 signaled(有信号)状态。
一旦线程成功获取了锁,就可以安全地访问被保护的资源,例如更新全局变量 max_value。在这之后,线程应该调用 ReleaseMutex(max_value_mutex) 来释放互斥体对象的锁,以允许其他线程访问受保护的资源。
总的来说,WaitForSingleObject(max_value_mutex, INFINITE) 用于等待获取互斥体对象的锁,以保护共享资源的访问。
1、线程创建函数
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
/*
* lpThreadAttributes:指向 SECURITY_ATTRIBUTES 结构的指针,用于指定新线程的安全性属性。一般情况下设为 NULL。
* dwStackSize:新线程的堆栈大小,一般设为 0 使用默认大小。
* lpStartAddress:指向线程函数的指针,即新线程的入口点。
* lpParameter:传递给线程函数的参数,可以是任意类型的指针。
* dwCreationFlags:线程创建的标志,一般设为 0。
* lpThreadId:指向一个 DWORD 变量的指针,用于接收新线程的标识符。
*/