2.Win32程序的执行单元

  • 多线程
    CreateProcess函数创建了进程,同时也创建了进程的主线程
    主线程在运行过程中可以创建新的线程,在同一进程中运行不同线程的好处是可以共享进程的资源,如全局变量、句柄等。
    当然各个线程也可以有自己的私有堆栈用于保存私有数据。

  • 线程的创建
    主线程的进入点是函数main
    辅助线程的进入点函数是线程函数ThreadProc

  • 线程函数ThreadProc

DWORD WINAPI ThreadProc(LPVOID lpParam); //线程函数名称ThreadProc可以是任意的
windef.h头下定义
#define WINAPI __stdcall;
__stdcall #从右到左传参,自动清栈
__cdecl   #从右到左传参,自动清栈
windows如何没有显示声明函数,则为__cdecl传参
  • CreateThread
    用来创建新线程
HANDLE  CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程的安全属性
	DWORD dwStackSize, //指定线程堆栈的大小
	LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的起始地址
	LPVOID lpParaneter, //传递给线程函数的参数
	DWORD dwCreationFlags, //指定创建线程后是否立即启动
	DWORD* lpThreadId //用于取得内核给新生成的线程分配的线程ID号
);
// 03ThreadDemo.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	int i=0;
	while(i<20)
	{
		printf("I am from a thread,count=%d\n",i++);
	}
	return 0;
}



int main(int argc, char* argv[])
{
	HANDLE hThread;
	DWORD dwThreadId;

	//创建一个线程
	hThread = CreateThread(
		NULL,   //默认安全属性
		NULL,   //默认堆栈大小
		ThreadProc, //线程入口地址(执行线程的函数)
		NULL,    //传给函数的参数
		0,       //指定线程立即运行
		&dwThreadId  //返回线程的ID号
		);
	printf("Now another thread has been created,ID = %d\n",dwThreadId);

	//等待新线程运行结束
	WaitForSingleObject(hThread,INFINITE);
	CloseHandle(hThread);



	return 0;
}

在这里插入图片描述

  • WaitForSingleObject
    用于等待指定的对象(hHandle)变成受信状态
WaitForSingleObject(
	hThread, //hHandle 要等待的对象的句柄
	INFINITE,  //dwMilliseconds 要等待的时间(毫秒为单位)
)
函数返回:
1. 要等待的对象变成受信(signaled)状态
2. 参数dwMilliseconds指定的时间已过去
  • 可执行对象的两种状态
    一种是受信状态(signaled)和未受信(nonsignaled)状态。线程对象只有当线程运行结束时才达到受信状态。

+线程内核对象
线程内核对象是一个包含了线程状态信息的数据结构。该结构信息如下所示。
在这里插入图片描述

1. 线程上下文CONTEXT
每个线程都有它自己的一组CPU寄存器,称为线程的上下文,该组寄存器的值保存在一个CONTEXT结构里,反映了该线程上次运行时CPU寄存器的状态。
2. 使用计数Usage Count
线程内核对象的存在与Usage Count的值息息相关,当这个值为0时,系统认为已没有任何进程引用次内核对象了,于是线程就要从内存中撤销。
创建一个新的线程后,初始状态下Usage Count=2,打开此内核对象Usage Count会加1,比如使用OpenThread函数打开线程时Usage Count就会加1,
但使用后一定要使用CloseHandle函数进行关闭,关闭会导致Usage Count减1,如果不关闭将导致内存泄漏
HANDLE GetCurrentProcess(); //返回当前进程句柄
HANDLE GetCurrentThread(); //返回当前线程句柄
返回内核对象时,不会到导致Usage Count增减变化。
3. 暂停次数Suspend Count
线程内核对象中的Suspend Count用于指明线程的暂停计数。当调用CreateProcess或CreateThread函数时,线程内核对象被创建,其暂停计数被初始化为1(即处于暂停状态)。
当CreateProcess或CreateThread传递CREATE_SUSPEND标志时,函数就返回,新线程处于暂停状态,如果尚未传递,暂停次数将被递减为0,此时线程处于可调度状态。
DWORD ResumeThread(HANDLE hThread); //唤起一个挂起的线程
DWORD SuspendThread(HANDLE hThread); //挂起一线程
4. 退出代码(Exit Code)
在线程运行期间,线程函数还没有返回,Exit Code=STILL_ACTIVE,线程运行结束后,系统自动将Exit Code设为线程得到返回值
DWORD  GetExitCodeThread(hThread,&dwExitCode)得到线程的退出代码
5. 是否受信(signaled)
线程运行期间,Signaled的值永远为FALSE,即未受信。当线程结束后,Signaled=TRUE ,此时该对象的等待函数就会返回,如WaitForSingleObject函数
  • 线程的终止
    线程终止时,会发生下列事件:
1. 在线程函数中创建的所有C++对象将通过它们各自的析构函数被正确地销毁
2. 该线程使用的堆栈被释放
3. 系统将线程内核对象中的Exit Code的值由STILL_ACTIVE设置为线程函数的返回值
4. 系统将递减线程内核对象中的Usage Count的值
  • 终止线程的执行
1.线程函数自然退出(最佳推荐)
2.使用ExitThread函数来终止,但c/c++资源却不能得到正确的清除
void ExitThread(DWORD dwExitCode//线程的退出代码
);
3.使用TerminateThread函数,会导致线程使用的堆栈不会被释放,强烈建议不使用
BOOL TerminateThread(
	HANDLE hThread,   //目标线程句柄
	DWORD dwExitCode  //目标线程的退出代码
);
4.ExitProcess,相当于对进程中的每个线程使用TerminateThread函数
  • 线程的优先级
    0(最低)-31(最高),windows系统支持6个优先级类:idle、below normal、normal、above normal、high、real-time
BOOL SetThreadPriority(HANDLE hThread,int nPriority);

nPriority参数
THREAD_PRIORITY_TIME_CRITICAL  //实时
THREAD_PRIORITY_HIGHEST       //最高
THREAD_PRIORITY_ABOVE_NORMAL  //高于正常
THREAD_PRIORITY_NORMAL     //正常
THREAD_PRIORITY_BLEOW_NORMAL  //低于正常
THREAD_PRIORITY_LOWEST   //最低
THREAD_PRIORITY_IDLE   //空闲
  • 示例:不同优先级的线程
// 03PriorityDemo.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

DWORD WINAPI ThreadIdle(LPVOID lpParam)
{
	int i = 0;
	while(i++ < 10)
		printf("Idle Thread is running\n");
	return 0;

}

DWORD WINAPI ThreadNormal(LPVOID lpParam)
{
	int i = 0;
	while(i++ < 10)
		printf("Normal Thread is running\n");
	return 0;

}


int main(int argc, char* argv[])
{
	DWORD dwThreadID;
	HANDLE h[2];

	//创建一个优先级为Idle的线程
	h[0] = CreateThread(NULL,0,ThreadIdle,NULL,CREATE_SUSPENDED,&dwThreadID);
	SetThreadPriority(h[0],THREAD_PRIORITY_IDLE);
	ResumeThread(h[0]);


	//创建一个优先级为Normal的线程
	//h[1] = CreateThread(NULL,0,ThreadNormal,NULL,0,&dwThreadID);
	h[1] = CreateThread(NULL,0,ThreadNormal,NULL,CREATE_SUSPENDED,&dwThreadID);
    SetThreadPriority(h[1],THREAD_PRIORITY_NORMAL);
	ResumeThread(h[1]);

	//等待两个线程内核对象都变成受信状态
	WaitForMultipleObjects(
		2,       //DWORD nCount 要等待的内核对象的数量
		h,       //CONST HANDLE *lpHandles 句柄数组
 		TRUE,    //BOOL bWaitAll 指定是否等待所有内核对象变成受信状态
		INFINITE //DwORD dwMilliseconds 要等待的时间
		);

	CloseHandle(h[0]);
	CloseHandle(h[1]);

	return 0;
}

在这里插入图片描述

  • c/c++版CreateThread函数——__beginthreadex
    使用时需要强制转化(HANDLE)
unsigned long __beginthreadex(
	void *security,
	unsigned stack_size,
	unsigned (__stdcall* start_address)(void *),
	void * arglist,
	unsigned initflag,
	unsigned *thrdaddr
);
  • 线程同步
    同步可以保证在一个时间内只有一个线程对某个共享资源有控制权。
// 03CountError.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include "process.h"

int g_nCount1 = 0;
int g_nCount2 = 0;
BOOL g_bContinue = TRUE;
UINT __stdcall ThreadFunc(LPVOID);



int main(int argc, char* argv[])
{
	UINT uId;
	HANDLE h[2];

	h[0] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);
	h[1] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);

	//等待1秒后通知两个计数线程结束,关闭句柄
	Sleep(1000);
    g_bContinue = FALSE;
	WaitForMultipleObjects(2,h,TRUE,INFINITE);
	CloseHandle(h[0]);
	CloseHandle(h[1]);

	printf("g_nCount1=%d\n",g_nCount1);
	printf("g_nCount2=%d\n",g_nCount2);
	


	return 0;
}

UINT __stdcall ThreadFunc(LPVOID)
{
	while(g_bContinue)
	{
		g_nCount1++;
		g_nCount2++;

	}
	return 0;

}

在这里插入图片描述

  • 临界区对象
    临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,windows内部使用这个记录一些信息,确保在同一个时间内只有一个线程访问该数据段中的数据。临界区对象能够很好地保护共享资源,但是它不能够用于进程之间资源的锁定,因为它不是内核对象。
    把临界区对象定义在想保护的数据段中,在任何线程使用此临界区对象之前对它进行初始化。
void InitializeCriticalSection(
	LPCRITICAL_SECTION lpCriticalSection //指向数据段中定义的CRITICAL_SECTION结构
);
线程访问临界区中的时候,必须首先调用EnterCriticalSection函数,申请进入临界区,同一时间内,windows只允许一个线程进入临界区
void EnterCriticalSection(
	LPCRITICAL_SECTION lpCriticalSection
);
线程操作数据完成时,将临界区交还给windows,使用LeaveCriticalSection函数完成
void LeaveCriticalSection(
	LPCRITICAL_SECTION lpCriticalSection
);
当程序不再使用临界区对象时,必须删除它
void DeleteCriticalSection(
	LPCRTITICAL_SECTION lpCriticalSection
);
  • 示例:使用临界区对象
// 03CriticalSection.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <process.h>
#include "windows.h"
#include "stdio.h"



BOOL g_bContinue = TRUE;
int g_nCount1=0;
int g_nCount2=0;
CRITICAL_SECTION g_cs; //对存在同步问题的代码段使用临界区对象

UINT __stdcall ThreadFunc(LPVOID);

int main(int argc, char* argv[])
{
	UINT uId;
	HANDLE h[2];

	//初始化临界区对象
	InitializeCriticalSection(&g_cs);

	h[0] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);
	h[1] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);

	Sleep(1000);
	g_bContinue = FALSE;
	WaitForMultipleObjects(2,h,TRUE,INFINITE);
	CloseHandle(h[0]);
	CloseHandle(h[1]);

	DeleteCriticalSection(&g_cs);

	printf("g_nCount1 = %d\n",g_nCount1);
	printf("g_nCount2 = %d\n",g_nCount2);



	return 0;
}

UINT __stdcall ThreadFunc(LPVOID)
{
	while(g_bContinue)
	{
		EnterCriticalSection(&g_cs);
		g_nCount1++;
		g_nCount2++;
		LeaveCriticalSection(&g_cs);
	
	}
	return 0;

}

在这里插入图片描述

  • 互锁函数
    互锁函数为了同步访问多线程共享变量提供了一个简单的机制。如果变量存在共享内存,不同进程的线程也可以使用此机制。

  • InterlockedIncrement和InterlockedDecrement
    InterlockedIncrement函数将递增(加1)指定的32位变量。这个函数可以阻止其他线程同时使用此变量。
    InterlockedDecrement函数将递减(减1)指定的32位变量。

LONG InterlockedIncrement(
	LONG volatile* Addend //指向要递增的变量
);
LONG InterlockedDecrement(
	LONG volatile* Addend //指向要递减的变量
);
  • 示例:使用互锁函数实现上面的线程同步功能


#include "stdafx.h"
#include <process.h>
#include "windows.h"
#include "stdio.h"



BOOL g_bContinue = TRUE;
int g_nCount1=0;
int g_nCount2=0;


UINT __stdcall ThreadFunc(LPVOID);

int main(int argc, char* argv[])
{
	UINT uId;
	HANDLE h[2];

	

	h[0] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);
	h[1] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);

	Sleep(1000);
	g_bContinue = FALSE;
	WaitForMultipleObjects(2,h,TRUE,INFINITE);
	CloseHandle(h[0]);
	CloseHandle(h[1]);

	

	printf("g_nCount1 = %d\n",g_nCount1);
	printf("g_nCount2 = %d\n",g_nCount2);



	return 0;
}

UINT __stdcall ThreadFunc(LPVOID)
{
	while(g_bContinue)
	{

		InterlockedIncrement((long*)&g_nCount1);
		InterlockedIncrement((long*)&g_nCount2);
	
	}
	return 0;

}
  • 事件内核对象
    多线程程序设计大多会涉及线程间相互通信,一种比较好的通信方法是使用事件内核对象。
    事件对象(event)是一种抽象对象,具备未受信和受信2种状态,它的状态设置和测试工作由windows来完成。
事件对象
1.nUsageCount
当计数为0时,windows销毁此内核对象占用的资源
2.bManualReset
指定在一个事件内核对象上等待的函数返回之后,windows是否重置这个对象为未受信状态
3.bSignaled
指定当前事件内核对象是否受信
  • 事件对象用法
1.CreateEvent
HANDLE CreateEvent(
	LPSECURITY_ATTRIBUTES lpEventAttributes, //用来定事件对象的安全属性
	BOOL bManualReset,                      //指定是否需要手动重置对象为未受信状态
	BOOL bInitialState,       //指定事件对象创建时的初始状态
	LPCWSTR lpName              //事件对象的名称
	);
参数bManualReset:
分为自动重置和人工重置,当一个人工重置的事件对象受信以后,所有等待在这个事件上的线程都会变为可调度状态。
当一个自动重置的事件对象受信以后,windows只允许一个等待在该事件上的线程变成可调度状态,然后就自动重置此事件对象为未受信状态

HANDLE OpenEvent(
	DWORD dwResiredAccess, //指定想要的访问权限
	BOOL bInheritHandle,  //指定返回句柄是否可被继承
	LPCWSTR lpName     //要打开的事件对象的名称
);
打开或创建事件对象之后,使用CloseHandle释放它占用的资源
BOOL SetEvent(HANDLE hEvent);   //将事件状态设为“受信(signaled)”
BOOL ResetEvent(HANDLE hEvent);  //将事件状态设为"未受信(nonsignaled)"
特别地,对一个自动重置类型对象,当在这样的事件对象上等待的函数(比如WaitForSingleObject函数)返回时,
windows会自动重置事件对象为未受信状态。通常情况下,为一个自动重置类型的事件对象调用ResetEvent函数是不必要的,因为windows会自动重置此对象
  • 示例:事件对象使用
// 03EventDemo.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include <process.h>

HANDLE g_hEvent;
UINT __stdcall ChildFunc(LPVOID);

int main(int argc, char* argv[])
{
	HANDLE hChildThread;
	UINT uId;

	//创建一个自动重置的,未受信的事件内核对象
	g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

	hChildThread = (HANDLE)_beginthreadex(NULL,0,ChildFunc,NULL,0,&uId);

	//通知子线程开始工作
	printf("Please input a char to tell the Child Thread to work:\n");
	getchar();
	SetEvent(g_hEvent);

	//等待子线程完成工作,释放资源
	WaitForSingleObject(hChildThread,INFINITE);
	printf("All the work has benn finished.\n");
	CloseHandle(hChildThread);
	CloseHandle(g_hEvent);
	



	return 0;
}

UINT __stdcall ChildFunc(LPVOID)
{
	WaitForSingleObject(g_hEvent,INFINITE);
	printf("Child thread is working .....\n");
	Sleep(5*1000);
	return 0;

}

在这里插入图片描述

  • 线程局部存储(TLS)
    线程局部存储(Thread Local Storage,TLS)是一个使用很方便的存储线程局部数据得系统。利用TLS机制可以为进程中所有的线程关联若干个数据,
    各个线程通过由TLS分配的全局索引来访问与自己关联的数据。这样,保证了每个线程都可以有线程局部的静态存储数据。
    windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间,如下图所示
    在这里插入图片描述

位数组的成员是一个标志,每个标志的值为FREE或INUSE,windows系统至少保证有TLS_MINIMUM_AVAILABLE(WinNT.h文件中定义)个标志位可用

  • 动态使用TLS典型步骤如下:
1.主线程调用TlsAlloc为线程局部存储分配索引
DWORD TlsAlloc(void); //返回一个TLS索引
2.每个线程调用TlsSetValue和TlsGetValue设置或读取线程数组中的值
BOOL TlsSetValue(
	DWORD dwTlsIndex,   //TLS索引
	LPVOID lpTlsValue  //要设置的值
);

LPVOID TlsGetValue(
	DWORD dwTlsIndex   //TLS索引
);
3.主线程调用TlsFree释放局部存储索引。函数的唯一参数是TlsAlloc返回的索引
  • 示例:使用TLS
// 03UseTLS.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <stdio.h>

//利用TLS跟踪线程的运行时间
DWORD g_tlsUsedTime;
void InitStartTime();
DWORD GetUsedTime();

UINT __stdcall ThreadFunc(LPVOID)
{
	int i;

	//初始化开始时间
	InitStartTime();

	//模拟长时间工作
	i = 1000*1000;
	while(i--){}

	//打印出本线程运行的时间
	printf("This thread is comming to end.ThreadID:%d,Used Time:%d\n",GetCurrentThreadId(),GetUsedTime());
	return 0;

}




int main(int argc,char* argv[])
{
 	UINT uId;
	int i;
	HANDLE h[10];

	//通过在进程位数组中申请一个索引,初始化线程运行时间记录系统
	g_tlsUsedTime = TlsAlloc();
	
	//十个线程同时运行,并等待它们各自的输出结果
	for(i=0;i<10;i++)
	{
		h[i] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);
	}
	for(i=0;i<10;i++)
	{
		WaitForSingleObject(h[i],INFINITE);
		CloseHandle(h[i]);
	}

	TlsFree(g_tlsUsedTime);

    

	return 0;
}

//初始化线程的开始时间
void InitStartTime()
{
	//获得当前时间,将线程的创建时间与线程对象相关联
	DWORD dwStart = GetTickCount();
	TlsSetValue(g_tlsUsedTime,(LPVOID)dwStart);

}

//取得一个线程已运行的时间
DWORD GetUsedTime()
{
	//获得当前时间,返回当前时间和线程创建时间的差值
	DWORD dwElapsed = GetTickCount();
	dwElapsed = dwElapsed-(DWORD)TlsGetValue(g_tlsUsedTime);
	return dwElapsed;

}




在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值