zoukankan      html  css  js  c++  java
  • Windows编程(多线程)

    Windows编程(多线程)

    线程创建函数

    CreateThread

    CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。

    HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES   lpThreadAttributes,
      SIZE_T                  dwStackSize,
      LPTHREAD_START_ROUTINE  lpStartAddress,
      __drv_aliasesMem LPVOID lpParameter,
      DWORD                   dwCreationFlags,
      LPDWORD                 lpThreadId
    );
    

    · 第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

    · 第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

    · 第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

    · 第四个参数 lpParameter 是传给线程函数的参数。

    · 第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

    · 第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

    #include <Windows.h>
    #include <stdio.h>
    #include <process.h>
    
    //HANDLE
    //WINAPI
    //CreateThread(
    //    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    //    _In_ SIZE_T dwStackSize,
    //    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    //    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    //    _In_ DWORD dwCreationFlags,
    //    _Out_opt_ LPDWORD lpThreadId
    //);
    DWORD WINAPI ThreadFun(LPVOID p) {
    	int a = *(int*)p;
    
    	printf("我是子线程,PID = %d,iMym = %d
    ", GetCurrentThreadId(), a);
    	
    	return 0;
    }
    
    
    int main() {
    	HANDLE ZThread;
    	DWORD dwThreadID;
    	int a = 100;
    	ZThread = CreateThread(NULL, 0, &ThreadFun, &a, 0, &dwThreadID);
    	
    }
    
    

    _beginthreadex

    _ACRTIMP uintptr_t __cdecl _beginthreadex(
        _In_opt_  void*                    _Security,
        _In_      unsigned                 _StackSize,
        _In_      _beginthreadex_proc_type _StartAddress,
        _In_opt_  void*                    _ArgList,
        _In_      unsigned                 _InitFlag,
        _Out_opt_ unsigned*                _ThrdAddr
        );
    
    unsigned WINAPI FUNC(VOID * p) {
    	int a = *(int*)p;
    	printf("我是子线程,PID = %d,iMym = %d
    ", GetCurrentThreadId(), a);
    	return 0;
    }
    
    int main() {
    	HANDLE hThread;
    	unsigned dwThreadID;
    	int a = 1;
    
    	 hThread= (HANDLE)_beginthreadex(NULL, 0, FUNC, (void*)&a,0,&dwThreadID);
    	 Sleep(3000);
    	
    }
    
    

    1 理解内核对象

    ​ 1 定义:

    内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存,由操作系统内核分配,并且只能由操作系统内核访问。在此数据结构中少数成员如安全描述符和使用计数是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。内核对象的数据结构只能由操作系统提供的API访问,应用程序在内存中不能访问。调用创建内核对象的函数后,该函数会返回一个句柄,它标识了所创建的对象。它可以由进程的任何线程使用。

    CreateProcess

    CreateThread

    CreateFile

    event

    Job

    Mutex

    常见的内核对象 : 进程、线程、文件,存取符号对象、事件对象、文件对象、作业对象、互斥对象、管道对象、等待计时器对象,邮件槽对象,信号对象

    ​ 内核对象:为了管理线程/文件等资源而由操作系统创建的数据块。

    ​ 其创建的所有者肯定是操作系统。

    WaitForSingleObject

    等待指定对象处于信号状态或超时间隔结束。

    DWORD WaitForSingleObject(
      HANDLE hHandle,//指明一个内核对象的句柄
      DWORD  dwMilliseconds //等待时间
    );
    

    hHandle:

    对象的句柄。有关可以指定句柄的对象类型的列表,请参阅以下备注部分。

    如果此句柄在等待仍处于挂起状态时关闭,则函数的行为未定义。

    句柄必须具有SYNCHRONIZE访问权限。有关更多信息,请参阅 标准访问权限

    dwMilliseconds:

    超时间隔,以毫秒为单位。如果指定了非零值,则函数会等待,直到对象发出信号或间隔结束。如果dwMilliseconds为零,如果对象没有发出信号,函数不会进入等待状态;它总是立即返回。如果dwMillisecondsINFINITE,则该函数将仅在对象收到信号时返回。

    int main()
    {
    	printf("main begin
    ");
    	int iParam = 5;
    	unsigned int dwThreadID;
    	DWORD wr;
    
    	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,
    		(void*)&iParam, 0, &dwThreadID);
    
    	if (hThread == NULL)
    	{
    		puts("_beginthreadex() error");
    		return -1;
    	}
    	// 
    	printf("WaitForSingleObject begin
    ");
    	wr = WaitForSingleObject(hThread, INFINITE);
    	/*if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
    	{
    		puts("thread wait error");
    		return -1;
    	}*/
    	printf("WaitForSingleObject end
    ");
    
    	printf("main end
    ");
    	system("pause");
    	return 0;
    }
    

    线程同步

    互斥对象

    互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。

    互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

    创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。

    请求互斥对象所有权:调用函数WaitForSingleObject函数。线程必须主动请求共享对象的所有权才能获得所有权。

    释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。

    CreateMutex
    HANDLE
    WINAPI
    CreateMutexW(
        _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全属性
        _In_ BOOL bInitialOwner,   //初始化互斥对象的所有者  TRUE 立即拥有互斥体,false表示创建的这个mutex不属于任何线程;所以处于激发状态,也就是有信号状态
        _In_opt_ LPCWSTR lpName    //指向互斥对象名的指针  L“Bingo”
        );
    

    WaitForMultipleObjects 函数

    DWORD WaitForMultipleObjects(
      DWORD        nCount,
      const HANDLE *lpHandles,
      BOOL         bWaitAll,
      DWORD        dwMilliseconds
    );
    

    参数

    nCount:

    lpHandles指向的数组中的对象句柄数。对象句柄的最大数量是MAXIMUM_WAIT_OBJECTS。此参数不能为零。

    lpHandles:

    对象句柄数组。有关可以指定句柄的对象类型的列表,请参阅以下备注部分。该数组可以包含不同类型对象的句柄。它可能不包含同一句柄的多个副本。

    如果这些句柄之一在等待仍然挂起时关闭,则函数的行为是未定义的。

    句柄必须具有SYNCHRONIZE访问权限。有关更多信息,请参阅 标准访问权限

    bWaitAll:

    如果此参数为TRUE,则当lpHandles数组中的所有对象的状态发出信号时,该函数返回。如果为FALSE,则当任何一个对象的状态设置为有信号时,该函数返回。在后一种情况下,返回值指示其状态导致函数返回的对象。

    dwMilliseconds:

    超时间隔,以毫秒为单位。如果指定了非零值,则函数将等待,直到指定的对象发出信号或间隔过去。如果dwMilliseconds为零,如果指定的对象没有发出信号,函数不会进入等待状态;它总是立即返回。如果dwMillisecondsINFINITE,则该函数将仅在指定对象发出信号时返回。

    视窗XP,Windows Server 2003和Windows Vista中,Windows 7和Windows Server 2008和Windows Server 2008 R2dwMilliseconds值不包括在低功耗状态所花费的时间。例如,当计算机处于睡眠状态时,超时确实会继续倒计时。

    Windows 8中,Windows Server 2012中的Windows 8.1,Windows Server 2012中R2中,Windows 10和Windows Server 2016dwMilliseconds值不包括在低功耗状态所花费的时间。例如,当计算机处于睡眠状态时,超时不会一直倒计时。

    #include <Windows.h>
    #include <stdio.h>
    #include <process.h>
    
    HANDLE hMutex;
    #define NUM_THREAD	50//线程数
    long long num = 0;
    
    unsigned WINAPI ThreadFun1(void* arg)
    {
    	int i;
    
    	WaitForSingleObject(hMutex, INFINITE);
    	for (i = 0; i < 500000; i++)
    		num += 1;
    	ReleaseMutex(hMutex);
    
    	return 0;
    }
    unsigned WINAPI ThreadFun2(void* arg)
    {
    	int i;
    
    	WaitForSingleObject(hMutex, INFINITE);
    	for (i = 0; i < 500000; i++)
    		num -= 1;
    	ReleaseMutex(hMutex);
    
    	return 0;
    }
    
    int main()
    {
    	printf("main begin
    ");
    	int iParam = 5;
    
    	hMutex = CreateMutexW(NULL, FALSE, 0);
    	HANDLE tHandles[NUM_THREAD];
    
    	for (size_t i = 0; i < NUM_THREAD; i++)
    	{
    		if (i % 2)
    			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, NULL, 0, NULL);
    		else
    			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, NULL, 0, NULL);
    	}
    
    	printf("WaitForSingleObject begin
    ");
    	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
    	
    	printf("WaitForSingleObject end
    ");
    	printf("result: %lld 
    ", num);
    	printf("main end
    ");
    	system("pause");
    	return 0;
    
    	
    	}
    
    

    这里建立2个方法,使用50个线程调度。一个方法对num值进行加一,一个方法对num进行减一处理。

    来验证结果线程锁是否起作用。

    事件对象

    事件对象也属于内核对象,它包含以下三个成员:

    ​ ● 使用计数;

    ​ ● 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;

    ​ ● 用于指明该事件处于已通知状态还是未通知状态的布尔值。

    ​ 事件对象有两种类型:人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;而当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

    1. 创建事件对象

    ​ 调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。

    1. 设置事件对象状态

    ​ 调用SetEvent函数把指定的事件对象设置为有信号状态。

    1. 重置事件对象状态

    ​ 调用ResetEvent函数把指定的事件对象设置为无信号状态。

    1. 请求事件对象

    线程通过调用WaitForSingleObject函数请求事件对象。

    HANDLE CreateEvent(   
    LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性   
    BOOL bManualReset,   // 复位方式  TRUE 必须用ResetEvent手动复原  FALSE 自动还原为无信号状态
    BOOL bInitialState,   // 初始状态   TRUE 初始状态为有信号状态  FALSE 无信号状态
    LPCTSTR lpName     //对象名称  NULL  无名的事件对象 
    );
    
    #include <Windows.h>
    #include <stdio.h>
    #include <process.h>
    
    #define STR_LEN		100
    static char str[STR_LEN];
    static HANDLE hEvent;
    
    unsigned WINAPI NumberOfA(void* arg);
    
    
    
    unsigned WINAPI NumberOfOthers(void* arg);
    
    unsigned WINAPI NumberOfA(void* arg)
    {
    	int i, cnt = 0;
    	//再没有执行fputs("Input string: ", stdout);
    	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
    	//WaitForSingleObject
    	WaitForSingleObject(hEvent, INFINITE);
    	for (i = 0; str[i] != 0; i++)
    	{
    		if (str[i] == 'A')
    			cnt++;
    	}
    	printf("Num of A: %d 
    ", cnt);
    	return 0;
    }
    unsigned WINAPI NumberOfOthers(void* arg)
    {
    	int i, cnt = 0;
    
    	for (i = 0; str[i] != 0; i++)
    	{
    		if (str[i] != 'A')
    			cnt++;
    	}
    	printf("Num of others: %d 
    ", cnt - 1);
    	//把事件对象设置为有信号状态
    	SetEvent(hEvent);
    	return 0;
    }
    
    int main() {
    	HANDLE hThread1, hThread2;
    	fputs("Input string: ", stdout);
    	fgets(str, STR_LEN, stdin);
    
    	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    	hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL,0 ,NULL);
    	hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL,0 ,NULL);
    	WaitForSingleObject(hThread1, INFINITE);
    	WaitForSingleObject(hThread2, INFINITE);
    	ResetEvent(hEvent);
    	CloseHandle(hEvent);
    	return 0;
    }
    

    关键代码段

    调用InitializeCriticalSection函数初始化一个关键代码段。

    InitializeCriticalSection(
        _Out_ LPCRITICAL_SECTION lpCriticalSection
        );
    

    该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SECTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

    1. 初始化关键代码段

    ​ 调用InitializeCriticalSection函数初始化一个关键代码段。

    InitializeCriticalSection(
    
      _Out_ LPCRITICAL_SECTION** lpCriticalSection
    
      );
    

    该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SECTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

    进入关键代码段

    VOID
    
    WINAPI
    
    EnterCriticalSection(
    
      _Inout_ LPCRITICAL_SECTION lpCriticalSection
      );
    

    调用EnterCriticalSection函数,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则该函数就返回;否则该函数会一直等待,从而导致线程等待。

    1. 退出关键代码段
    VOID
    
    WINAPI
    
    LeaveCriticalSection(
    
      _Inout_ LPCRITICAL_SECTION lpCriticalSection);
    

    线程使用完临界区所保护的资源之后,需要调用LeaveCriticalSection函数,释放指定的临界区对象的所有权。之后,其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。

    删除临界区

    WINBASEAPI
    
    VOID
    
    WINAPI
    
    DeleteCriticalSection(
    
      _Inout_ LPCRITICAL_SECTION lpCriticalSection
    
      );
    

    当临界区不再需要时,可以调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。

    #include <stdio.h>
    #include <windows.h>
    #include <process.h> 
    
    
    int iTickets = 5000;
    CRITICAL_SECTION g_cs;
    
    // A窗口     B窗口
    DWORD WINAPI SellTicketA(void* lpParam)
    {
    	while (1)
    	{
    		EnterCriticalSection(&g_cs);//进入临界区
    		if (iTickets > 0)
    		{
    			Sleep(1);
    			iTickets--;
    			printf("A remain %d
    ", iTickets);
    			LeaveCriticalSection(&g_cs);//离开临界区
    		}
    		else
    		{
    			LeaveCriticalSection(&g_cs);//离开临界区
    			break;
    		}
    	}
    	return 0;
    }
    
    DWORD WINAPI SellTicketB(void* lpParam)
    {
    	while (1)
    	{
    		EnterCriticalSection(&g_cs);//进入临界区
    		if (iTickets > 0)
    		{
    			Sleep(1);
    			iTickets--;
    			printf("B remain %d
    ", iTickets);
    			LeaveCriticalSection(&g_cs);//离开临界区
    		}
    		else
    		{
    			LeaveCriticalSection(&g_cs);//离开临界区
    			break;
    		}
    	}
    	return 0;
    }
    
    int main()
    {
    	HANDLE hThreadA, hThreadB;
    	InitializeCriticalSection(&g_cs); //初始化关键代码段
    	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
    	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
    	CloseHandle(hThreadA); //1
    	CloseHandle(hThreadB); //1
    	Sleep(40000);
    	DeleteCriticalSection(&g_cs);//删除临界区
    	system("pause");
    
    	return 0;
    }
    
  • 相关阅读:
    打包CAB大全
    设置VC6为默认异常调试工具
    XP系统遍历所有进程
    编写有界面的系统服务程序
    GOOGLE C++编程规范
    编写有界面的系统服务程序
    VC下音频文件的播放
    用MFC建立COM服务器对象的框架步骤
    注册OCX失败:由于应用程序配置不正确,程序未能启动.重新安装应用程序可能会纠正这个错误
    MediaPlayer属性大全
  • 原文地址:https://www.cnblogs.com/nice0e3/p/15158981.html
Copyright © 2011-2022 走看看