代码改变世界

Windows多线程基础

2015-09-04 20:14  rangers  阅读(357)  评论(0编辑  收藏  举报

进程与线程基础

程序: 计算机指令的集合,以文件的形式存储在磁盘上
进程: 正在运行是程序实例,以是一个程序在其自身的地址空间的一次执行活动。进程有一个进程管理的内核对象和地址空间组成。
线程: 程序执行的最小单元。每个进程至少一个线程,进程是线程的容器。线程是CPU调度与运行的最小单位,而进程是资源分配的最小单位。线程由线程内核对象和线程栈组成。

Windows下线程的创建

windows下创建线程的API:

HANDLE WINAPI CreateThread(
    __in_opt  LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in      SIZE_T dwStackSize,
    __in      LPTHREAD_START_ROUTINE lpStartAddress,
    __in_opt __deref __drv_aliasesMem LPVOID lpParameter,
    __in      DWORD dwCreationFlags,
    __out_opt LPDWORD lpThreadId
    );

lpThreadAttributes: 线程内核对象的安全属性,传入NULL,表示使用默认安全属性。
dwStackSize: 线程栈空间大小,0表示默认大小
lpStartAddress: 新线程所执行的线程函数地址,该函数的名称随意,但必须遵照下面的声明形式:

DWORD WINAPI ThreadProc( LPVOID lpThreadParameter );  

lpParameter: 传递给线程函数的参数
dwCreationFlags: 控制线程创建的附加标志,设置为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread(), 如果是0,线程创建后立即执行。
lpThreadId: 该参数是一个返回值,用来接收线程ID.

线程创建实例:

#include <Windows.h>
//线程函数
DWORD _stdcall ThreadProc (LPVOID lPparameter)
{
	for (int i = 0; i < 100; ++i)
	{
		cout << "\n[" << i <<"] Thread1 running!";
	}
	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//创建线程
	HANDLE hThread;
	hThread = ::CreateThread(NULL,0,&ThreadProc,NULL,0,NULL);

	::WaitForSingleObject(hThread,INFINITE);	//等待子线程运行结束
	::CloseHandle(hThread);
	cout << "\nMain thread finished!";
	return 0;
}

注:上面的CloseHandle函数不是终止新创建的线程,而只是关闭线程句柄。当关闭该线程句柄时该线程的内核对象引用计数减1,当新线程执行结束后,内核对象引用计数也递减,当引用计数为0时,系统释放该线程内核对象。因此,在程序中,当不需要线程句柄时,因将它关闭。

_beginthreadex

当程序使用CRT(C运行库)时,尽量使用_beginthreadex _endthreadex来创建或结束进程。因为使用CreateThread创建进程时,如果使用了C运行库的一些函数,可能会造成一些问题。

_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()

还有一组类似的函数,AfxBeginThread()AfxEndThread(),它是MFC层面的线程包装函数,当使用MFC类库时,尽量使用这组函数来创建线程。

线程同步

同步: 按照预定的先后次序进行运行。

多线程面临的主要问题就是同步,实现线程同步的机制一般有四种:互斥量、信号量、事件、临界区。

线程同步例子:

int g_tickets = 100;
HANDLE g_hMutex; //通过互斥对象实现同步
HANDLE g_hEvent; //通过事件对象实现线程同步
CRITICAL_SECTION g_cs;	//使用临界区对象实现线程同步

unsigned int _stdcall ThreadProc1 (LPVOID lPparamter)
{
	bool sell_out = false;
	while (!sell_out)
	{
		//::WaitForSingleObject(g_hMutex,INFINITE);	//请求互斥对象
		//::WaitForSingleObject(g_hEvent,INFINITE);	//请求事件对象
		EnterCriticalSection(&g_cs); //进入临界区
		if (g_tickets > 0)
		{
			cout << "thread1 sell ticket: " << g_tickets-- << endl;
		}
		else
		{
			sell_out = true;
		}
		//::ReleaseMutex(g_hMutex);	//访问结束后,释放互斥对象
		//::SetEvent(g_hEvent); //将事件对象置为有信号状态,允许其他等待该对象的线程可调度
		::LeaveCriticalSection(&g_cs); //离开临界区
	}
	return 0;
}

unsigned int _stdcall ThreadProc2 (LPVOID lPparamter)
{
	bool sell_out = false;
	while (!sell_out)
	{
		//::WaitForSingleObject(g_hMutex,INFINITE);
		//::WaitForSingleObject(g_hEvent,INFINITE);	
		::EnterCriticalSection(&g_cs);
		if (g_tickets > 0)
		{
			cout << "thread2 sell ticket: " << g_tickets-- << endl;
		}
		else
		{
			sell_out = true;
		}
		//::ReleaseMutex(g_hMutex);
		//::SetEvent(g_hEvent); 
		::LeaveCriticalSection(&g_cs);
	}
	return 0;
}

typedef unsigned int  (_stdcall *pThreadProc)(LPVOID);

int _tmain(int argc, _TCHAR* argv[])
{
	const int THREADNUM = 2;
	pThreadProc threadProcs[THREADNUM];
	threadProcs[0] = &ThreadProc1;
	threadProcs[1] = &ThreadProc2;
	HANDLE hThreads[THREADNUM];
	for (size_t threadIndex = 0; threadIndex < THREADNUM; ++threadIndex)
	{
		hThreads[threadIndex] = (HANDLE)::_beginthreadex(NULL,0,threadProcs[threadIndex],NULL,0,NULL);
	}
	//g_hMutex = ::CreateMutex(NULL,false,NULL); //使用互斥对象实现线程同步
	//g_hEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);//使用自动重置事件对象实现同步
	//::SetEvent(g_hEvent);
	::InitializeCriticalSection(&g_cs);	//使用临界区对象实现线程同步
	//等待所有线程结束
	::WaitForMultipleObjects(THREADNUM,hThreads,TRUE,INFINITE);
	::DeleteCriticalSection(&g_cs); //释放临界区对象
	cout << "\nMain thread finished!";
	return 0;
}

使用互斥量、事件对象、临界区实现线程同步的总结:

互斥量和事件对象都属于内核对象,使用内核对象进行线程同步时,速度较慢。但使用内核对象可以在多个进程的各个线程间进行同步。
临界区同步方式工作在用户状态,同步速度快。故在编写多线程程序时,首选使用临界区对象进行同步。

多线程学习资料:
http://blog.csdn.net/morewindows/article/details/7392749
http://www.cnblogs.com/P_Chou/archive/2012/06/10/basic-of-thread.html
http://www.cnblogs.com/chengmin/archive/2011/09/26/2192421.html