Win32程序的执行单元

1.多线程

1.基本知识

一个进程的多个线程通过不同轮流获得CPU的时间片运行,使得系统能够在多线程下运行

每个线程必须有一个进入点函数,主线程的入口函数是main

其他线程函数 DWORD WINAPI ThreadProc(LPVOID lpParam);

其中#define WINAPI __stdcall 

stdcall 和cdecl区别:

1)采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。

 2)采用__stdcall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。

如果没有显示的说明的话,一般的函数都是__cdecl

API说明:

#include <stdio.h>
#include <windows.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,		// 指定线程立即运行,如果设置成CREATE_SUSPEND,则这个线程不会立即执行,会等到ResumeThread的时候再执行
		&dwThreadId);	// 返回线程的ID号
	printf(" Now another thread has been created. ID = %d \n", dwThreadId);

	/* ::WaitForSingleObject 继续执行的两个条件
		1.句柄受信
		2.时间到了
	*/
	// 等待新线程运行结束INFINITE,只有在句柄受信的情况下,才会继续执行	
	::WaitForSingleObject (hThread, INFINITE);
	::CloseHandle (hThread);
	return 0;
}

/*::CreateThread 第一个参数详解
typedef struct _SECURITY_ATTRIBUTES { 
	DWORD nLength;		//数据结构的大小
  	LPVOID lpSecurityDescriptor;
  	BOOL bInheritHandle;	//句柄是否可以被继承
}SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;
*/

 

2.什么是线程内核对象?

基本成员

1.CONTEXT

线程上下文是保存线程拥有的寄存器

2.使用计数器

CreateThread会使计数器为1,赋值个hThread,相当于又打开了一次此时,计数器为2,

线程结束的时候会将减1,然后closehandle变为0.此时系统才会释放内核对象

还有函数OpenThread也会加1,有些函数虽然也能获得句柄但是是伪句柄,计数器不会加1,所以对这些伪句柄close的话,只会返回false,不会减1

这样的函数有GetCurrentProcess,GetCurrentThread

3.暂停次数

CreateThread的时候先创建线程内核对象,然后暂停计数器变为1,接着初始化内核对象,最后判断CreateThread的参数是否指定CREATE_SUSPEND,如果不是则计数器变为0.

这样这个线程就可以调度,然后CPU每隔20ms左右会扫描可调度的线程,然后按照优先级分配cpu时间片

4.退出码

退出码就是线程函数的返回值,如果还在运行期间,这个返回码的值是STILL_ACTIVE.

5.是否受信

在线程运行期间,受信状态一直是false,结束的时候将受信状态变为哦true,会使得等待在这个受信状态的线程,继续

3.线程的优先级

 

#include <stdio.h>
#include <windows.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);
	
	// 等待两个线程内核对象都变成受信状态
	::WaitForMultipleObjects(
		2,	    // DWORD nCount  要等待的内核对象的数量
		h,	    // CONST HANDLE *lpHandles 句柄数组
		TRUE,	    // BOOL bWaitAll	指定是否等待所有内核对象变成受信状态
		INFINITE);  // DWORD dwMilliseconds 要等待的时间
   	
	::CloseHandle(h[0]);
	::CloseHandle(h[1]);

	return 0;
}

/*
	HANDLE h[2];
	h[0] = hThread1;
	h[1] = hThread2;
	DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000);
	switch(dw)
	{
	case WAIT_FAILED:
		// 调用WaitForMultipleObjects函数失败(句柄无效?)
		break;
	case WAIT_TIMEOUT:
		// 在5秒内没有一个内核对象受信
		break;
	case WAIT_OBJECT_0 + 0:
		// 句柄h[0]对应的内核对象受信
		break;
	case WAIT_OBJECT_0 + 1:
		// 句柄h[1]对应的内核对象受信
		break;
	}
*/


4.线程同步

1.使用临界区对象

a)定义一个全局的临界区对象 CRITICAL_SECTION g_cs;

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

c)进入临界区::EnterCriticalSection(&g_cs);

d)离开临界区::LeaveCriticalSection(&g_cs);

e)销毁临界区::DeleteCriticalSection(&g_cs);

2.互锁函数

一些线程共享变量需要一些简单的++.或者--,可以通过互锁函数代替.这样既可以保证是同步,可以实现++或者--

::InterlockedIncrement((long*)&g_nCount1);实现对g_nCount1 + 1

3.事件内核对象

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

HANDLE g_hEvent;
UINT __stdcall ChildFunc(LPVOID);

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

	// 创建一个自动重置的(auto-reset events),未受信的(nonsignaled)事件内核对象
	/*::CreateEvent
	第一个参数:安全属性
	第二个参数:是否手动重置,手动:当所有等待在这个event上的线程都运行.自动:	仅允许一个线程可调度,并且自动重置为未受信
	第三个参数:初始受信状态
	第四个参数:对象名称
	*/
	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();
	//::ResetEvent设为未受信
	//设置受信
	::SetEvent(g_hEvent);

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

UINT __stdcall ChildFunc(LPVOID)
{
	//等待g_hEvent受信
	::WaitForSingleObject(g_hEvent, INFINITE);
	printf("  Child thread is working...... \n");
	::Sleep(5*1000); // 暂停5秒,模拟真正的工作
	return 0;
}


4.信号量内核对象

允许多个线程同时访问同一个共享变量,但是可以设置同时允许访问的线程个数

 

HANDLE WINAPI CreateSemaphore(
  __in          LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  __in          LONG lInitialCount,
  __in          LONG lMaximumCount,
  __in          LPCTSTR lpName
);

 

第二个参数是当前可进入的线程数

第三个参数是总共有的资源

WaitForSingleObject 进入共享资源等待信号量

进入后资可用资源数减1

 结束时候

BOOL WINAPI ReleaseSemaphore(
  __in          HANDLE hSemaphore,
  __in          LONG lReleaseCount,
  __out         LPLONG lpPreviousCount
);

使得可用计数器加1

5.互斥内核对象

用法同信号量,但是只是运行一个线程

HANDLE WINAPI CreateMutex(
  __in          LPSECURITY_ATTRIBUTES lpMutexAttributes,
  __in          BOOL bInitialOwner,
  __in          LPCTSTR lpName
);

 

BOOL WINAPI ReleaseMutex(
  __in          HANDLE hMutex
);

 

5.线程局部存储

如果多个线程的入口函数相同,每个线程对于同一个变量应该有不同的值,所以就需要线程局部存储这个概念

 

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

// 利用TLS记录线程的运行时间

DWORD g_tlsUsedTime;
void InitStartTime();
DWORD GetUsedTime();


UINT __stdcall ThreadFunc(LPVOID)
{
	int i;

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

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

	// 打印出本线程运行的时间
	printf(" This thread is coming to end. Thread ID: %-5d, 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;
}

实际开发中,会用这个方法保存指针然后指针分别指向各个内存

TLS实现原理:
 

 

线程局部位数组标志这个每个线程对应的index是否已经被分配,如果没有分配发挥index,也就是代码中的g_tlsUsedTime,

比如:线程2要存储变量,位数组中index2为空闲,(表明所有线程的index都未空闲)g_tlsUsedTime为index2,然后在线程中讲值存入index2,不同线程存入不同的数组内的index2

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值