使用C++多线程编程

一.创建线程

最好使用C++运行库中的_beginthreadex()函数创建进程,_beginthreadex使用CreateThread实现的,但针对C++语言作了一些处理。详见《windows核心编程》第6章。
#pragma once

#include <windows.h>
#include <process.h>
#include <tchar.h>
#include <iostream>

using namespace std;

class ExampleTask
{
public:
	void startTask();
	unsigned static WINAPI taskMain(LPVOID param); //若线程调用函数为类成员则必须设置为static
};

unsigned WINAPI ExampleTask::taskMain(LPVOID param)
{
	cout<<"thread 1"<<endl;
	return 0;
}

void ExampleTask::startTask()
{
	unsigned uiThreadId; //进程id
	_beginthreadex(
		NULL,	//安全设置
		0,	//stacksize
		taskMain, //函数地址
		(PVOID) (INT_PTR) 1, //参数列表
		0,//0表示立即运行,CREATE_SUSPEND表示延迟执行
		&uiThreadId);
	cout<<"start thread 1"<<endl;
}

int _tmain(int argc,TCHAR* argv[])
{
	ExampleTask realTimeTask;
	realTimeTask.startTask();
	return 0;
}

使用RemuseThread()来继续执行进程:
调用WaitForSingleObject()函数让主线程等待子线程。
class Threadx
{
	int loopstart;
	int loopend;
	int frequent;

public:
	Threadx(int,int,int);
	unsigned static WINAPI ThreadStaticEntryPoint(LPVOID param);
	void ThreadEntry();

	string threadName;
};

Threadx::Threadx(int start,int lend,int freq):
	loopend(lend),loopstart(start),frequent(freq)
{
}

unsigned WINAPI Threadx::ThreadStaticEntryPoint(LPVOID param)
{
	Threadx* thread = reinterpret_cast<Threadx*>(param);
	thread->ThreadEntry();
	return 1;
}

void Threadx::ThreadEntry()
{
	for (int i = loopstart; i < loopend; ++i) {
		if (i % frequent == 0) {
			cout<<threadName<<" "<<i<<endl;
		}
	}
	cout<<threadName<<" end"<<endl;
}


int _tmain(int args,TCHAR* argv[])
{
	Threadx* th = new Threadx(0,20,2);
	HANDLE hth1;
	unsigned uiThreadId;
	hth1 = (HANDLE)_beginthreadex(
		NULL,	//security
		0,	//stack size
		Threadx::ThreadStaticEntryPoint,
		th, //
		CREATE_SUSPENDED, //延时创建,暂时不创建,0 for running or CREATE_SUSPENDED for suspended
		&uiThreadId
		);
	if (hth1 == 0) {
		cout<<"创建进程失败!"<<endl;
	}

	th->threadName = "thread1";
	DWORD   dwExitCode;  
	GetExitCodeThread( hth1, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259   
	printf( "initial thread 1 exit code = %u\n", dwExitCode );  
	ResumeThread(hth1);//使用resumeThread继续运行thread
	WaitForSingleObject( hth1, INFINITE );  


	Threadx* th2 = new Threadx(1,25,3);
	th2->threadName = "thread 2";
	unsigned th2id;
	HANDLE hth2 = (HANDLE)_beginthreadex(NULL,0,Threadx::ThreadStaticEntryPoint,th2,CREATE_SUSPENDED,&th2id);
	GetExitCodeThread( hth2, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259   
	printf( "initial thread 2 exit code = %u\n", dwExitCode );  
	ResumeThread(hth2);
/*
(1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。
所以上面的代码中如果不调用WaitForSingleObject,则2个子线程t1和t2可能并没有执行完毕或根本没有执行。

(2)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。
所以上面的代码如果不调用resumethread,则会死锁。
*/
	WaitForSingleObject( hth2, INFINITE );
	CloseHandle(hth1);
	CloseHandle(hth2);
	return 0;
}

二.终止线程

1.等待线程执行完自己return(推荐做法),然后系统会自动调用_endthreadex()函数释放进程。
2.在进程中调用_endthread(0)强行终止,可能存在资源没被释放就停止进程,导致内存泄露。

三.进程同步

实现线程同步的方法:
用户态:原子操作,临界区
内核态:事件,信号量,互斥量

1.利用原子操作实现进程同步:

/*
在下面的程序中,利用了全局变量ThreadData来进行线程间的同步,当子线程结束时改变该值,
而父线程则循环判断该值来确认子线程是否已经结束,当子线程结束时,父线程才继续进行下面的操作。
*/
volatile bool ThreadAtom = true;//运行子线程

unsigned WINAPI TreadProcess(LPVOID lParam)
{
	for (int i = 0; i < 10; ++i) {
		Sleep(1000);
		cout<<"子线程完成第 "<<i<<" 个任务"<<endl;
		if (i == 6) {
			//_endthreadex(0);//如果此处强行终止进程,threadatom就不会变成false主进程就会死循环
		}
	}
	cout<<"子线程结束"<<endl;
	ThreadAtom = false;

	return 1;
}

int _tmain(int argc,TCHAR* argv[])
{
	unsigned threadid;
	HANDLE thread = (HANDLE)_beginthreadex(NULL,0,TreadProcess,0,0,&threadid);
	if (thread == 0) {
		cout<<"创建进程失败!"<<endl;
	}

	while (ThreadAtom) {
		cout<<"主线程在等待子线程"<<endl;
		Sleep(1600);
	}
	//WaitForSingleObject(thread,INFINITE); //用waitforsingleobject也能达到同样的目的
	cout<<"主线程结束"<<endl;
	CloseHandle(thread);
	system("pause");
	return 0;
}

以上只有1个子线程所以就直接修改ThreadAtom的值,若有多个子线程,则需用Interlocked系列函数来处理:InterLockExchange(&ThreadAtom,false);(详见《windows 核心编程》第八章)。

2.临界区(Critical Section)

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
//-----------------------------------------------
//下面这个例子,进程ADD每次把g_num增加1,进程SUB每次把
//g_num的值减少2。如果g_num小于0,则结束进程
//-----------------------------------------------
int g_num = 20;
CRITICAL_SECTION g_cs;

unsigned WINAPI ThreadAdd(LPVOID lpvoid)
{
	while (true) {
		if (TryEnterCriticalSection(&g_cs)) { //尝试进入临界区
			if (g_num < 0) {
				LeaveCriticalSection(&g_cs);
				break;
			}
			Sleep(1300);
			cout<<"进程ADD进入临界区: g_num = "<<g_num<<endl;
			g_num += 1;
			cout<<"进程ADD离开临界区: g_num = "<<g_num<<endl;
			LeaveCriticalSection(&g_cs);//出临界区
		} else {
			Sleep(800);
			cout<<"临界区被Sub进程占用,下次再来吧"<<endl;
		}
	}
	
	cout<<"Add进程结束"<<endl;
	return 1;
}

unsigned WINAPI ThreadSubtract(LPVOID lpvoid)
{
	while (true) {
		TryEnterCriticalSection(&g_cs);//进入临界区
		if (g_num < 0) {
			LeaveCriticalSection(&g_cs);
			break;
		}
		Sleep(600);
		cout<<"进程SUB进入临界区: g_num = "<<g_num<<endl;
		g_num -= 2;
		cout<<"进程SUB离开临界区: g_num = "<<g_num<<endl;
		LeaveCriticalSection(&g_cs);//出临界区
	}
	
	cout<<"Sub进程结束"<<endl;
	return 1;
}

int _tmain(int argc,TCHAR* argv[])
{
	HANDLE		thAdd;
	HANDLE		thSub;
	unsigned	thAddId;
	unsigned	thSubId;
	InitializeCriticalSection(&g_cs);//初始化临界区变量
	thAdd = (HANDLE)_beginthreadex(NULL,0,ThreadAdd,0,0,&thAddId);
	thSub = (HANDLE)_beginthreadex(NULL,0,ThreadSubtract,0,0,&thSubId);
	if (thAdd == 0) {
		cout<<"创建Add进程失败!"<<endl;
	}
	if (thSub == 0) {
		cout<<"创建Sub进程失败!"<<endl;
	}
	WaitForSingleObject(thAdd,INFINITE);
	WaitForSingleObject(thSub,INFINITE);

	CloseHandle(thAdd);
	CloseHandle(thSub);
	DeleteCriticalSection(&g_cs); //释放临界区变量
	cout<<"主进程结束"<<endl;
	system("pause");
	return 0;
}

3.事件(Event)

事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。信号量包含的几个操作原语:

      CreateEvent()   // 创建一个信号量 
    OpenEvent() //   打开一个事件 
    SetEvent()   // 回置事件 
    WaitForSingleObject()  // 等待一个事件 
    WaitForMultipleObjects()//  等待多个事件
WaitForMultipleObjects 函数原型: 
    WaitForMultipleObjects( 
    IN DWORD nCount, // 等待句柄数 
    IN CONST HANDLE *lpHandles, //指向句柄数组 
    IN BOOL bWaitAll, //是否完全等待标志 
    IN DWORD dwMilliseconds //等待时间 
    )

下面来看一个例子:

#pragma once

#include <windows.h>
#include <tchar.h>
#include <process.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

//-----------------------------------------------
//2个线程协作反转字符串
//-----------------------------------------------

//
//下面例子中通过一个服务器线程和客户端线程来处理字符串:
//刚开始服务器处理等待状态,当客户端有请求的时候会先把请求发到一个共享内存缓冲中,
//并触发一个事件,这服务器线程就会去查看缓冲并处理客户端请求。当服务器处理请求的
//时候,客户端处于等待状态。
/

HANDLE g_clientEvent; //客户端有事件给服务器
HANDLE g_serverEvent; //服务器有数据给客户端

string buffer; //共享内存


unsigned WINAPI serverThread(LPVOID lparam)
{
	WaitForSingleObject(g_clientEvent,INFINITE); // INFINITE: 长时间等待,差不多50天
	cout<<"服务器接受到原始字符串:"<<buffer<<endl;
	reverse(buffer.begin(),buffer.end());
	cout<<"服务器处理字符串:"<<buffer<<endl;
	SetEvent(g_serverEvent);

	return 0;
}

unsigned WINAPI clientThread(LPVOID lparam)
{
	string newbuffer(reinterpret_cast<char*>(lparam));
	buffer = newbuffer;
	cout<<"客户端发送字符串:"<<buffer<<endl;
	SetEvent(g_clientEvent);
	WaitForSingleObject(g_serverEvent,INFINITE);
	cout<<"客户端接受到字符串:"<<buffer<<endl;

	return 0;
}

int _tmain(int argc,TCHAR* argv[])
{
	string buf = "reverse the string!"; 
	g_clientEvent = CreateEvent(
						NULL,	//SECURITY_ATTRIBUTES结构指针,可为NULL
						FALSE,	//TRUE: 在WaitForSingleObject后必须手动调用ResetEvent清除信号
								//FALSE:在WaitForSingleObject后,系统自动清除事件信号 
						FALSE,	//初始状态
						NULL);	//事件的名称
	g_serverEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	
	unsigned thid;
	HANDLE serverTh = (HANDLE)_beginthreadex(NULL,0,serverThread,NULL,0,&thid);
	HANDLE clientTh = (HANDLE)_beginthreadex(NULL,0,clientThread,(void*)buf.data(),0,&thid);
	Sleep(5000);	//让两个进程有时间执行完

	CloseHandle(clientTh);
	CloseHandle(serverTh);
	CloseHandle(g_clientEvent);
	CloseHandle(g_serverEvent);

	system("pause");
	return 0;
}

4.信号量(semaphore)

//-----------------------------------------------

//操作系统题目:爸爸往盘子中放苹果或者橘子,然后通知儿子来
//拿苹果,女儿来拿橘子。同时只能有一个人用盘子,盘子中最多
//只放一个水果
//-----------------------------------------------

#define P(S) WaitForSingleObject(S,INFINITE) //等待某资源
#define V(S) ReleaseSemaphore(S,1,NULL) //释放一个资源

HANDLE g_plate; //盘子
HANDLE g_apple; //苹果
HANDLE g_orange; //橘子

//儿子吃苹果
unsigned WINAPI EatApple(LPVOID lprama)
{
	while (true) {
		P(g_apple);
		P(g_plate);
		Sleep(100);
		cout<<"儿子吃掉了盘中的苹果"<<endl;
		V(g_plate);
	}
	return 0;
}

//女儿吃橘子
unsigned WINAPI EatOrange(LPVOID lprama)
{
	while (true) {
		P(g_orange);
		P(g_plate);
		Sleep(100);
		cout<<"女儿吃掉了盘中的橘子"<<endl;
		V(g_plate);
	}
	return 0;
}
/*
// HANDLE CreateSemaphore (  
//  PSECURITY_ATTRIBUTE psa,  
//  LONG lInitialCount, //开始时可供使用的资源数  
//  LONG lMaximumCount, //最大资源数  
//  PCTSTR pszName);  
*/
//主进程:爸爸往盘子中放水果
int _tmain(int argc,TCHAR* argv[])
{
	g_plate = CreateSemaphore(NULL,1,1,NULL);
	g_apple = CreateSemaphore(NULL,0,1,NULL);
	g_orange = CreateSemaphore(NULL,0,1,NULL);
	unsigned thid;
	HANDLE son = (HANDLE)_beginthreadex(NULL,0,EatApple,NULL,0,&thid);
	HANDLE daughter = (HANDLE)_beginthreadex(NULL,0,EatOrange,NULL,0,&thid);
	int input = 0; //输入0退出
	cout<<"开始运行:0退出,1放苹果,2放橘子"<<endl;
	while (true){
		cin>>input;
		if (input == 0) {
			break;
		} else if (input == 1) {
			cout<<"爸爸往盘子里放了一个苹果"<<endl;
			P(g_plate);
			V(g_apple);
			V(g_plate);
		} else if (input == 2) {
			cout<<"爸爸往盘子里放了一个橘子"<<endl;
			P(g_plate);
			V(g_orange);
			V(g_plate);
		}
		Sleep(150);
		cout<<"请输入0,1,2,继续放水果"<<endl;
	}
	CloseHandle(son);
	CloseHandle(daughter);
	CloseHandle(g_plate);
	CloseHandle(g_orange);
	CloseHandle(g_apple);
	return 0;
}

5.互斥量(Mutex)

两个线程同时写一个文件:

//-----------------------------------------------
//利用mutex实现进程同步:两个进程同时写文件
//-----------------------------------------------

HANDLE g_mutext;
int g_wid = 0;

void writeFile(char* vlist)
{
	ofstream file("log.txt",ofstream::app);
	if (!file) {
		cerr<<"打开文件失败!"<<endl;
		return;
	}
	file<<vlist<<endl;
	file.close();
}

unsigned WINAPI Writer1(LPVOID lparam)
{
	while (true){
		WaitForSingleObject(g_mutext,INFINITE);
		Sleep(1000);
		char str[200];
		sprintf(str,"Writer1 write:%d",g_wid++);
		cout<<str<<endl;
		writeFile(str);
		ReleaseMutex(g_mutext);
	}
	return 0;
}

unsigned WINAPI Writer2(LPVOID lparam)
{
	while (true){
		WaitForSingleObject(g_mutext,INFINITE);
		Sleep(1000);
		char str[200];
		sprintf(str,"Writer2 write:%d",g_wid++);
		cout<<str<<endl;
		writeFile(str);
		ReleaseMutex(g_mutext);
	}
	return 0;
}

int _tmain(int args,TCHAR* argv[])
{
	g_mutext = CreateMutex(NULL,FALSE,NULL);
	//第二个参数:创建者线程是否有mutex所有权
	unsigned thid;
	HANDLE thw1 = (HANDLE)_beginthreadex(NULL,0,Writer1,NULL,0,&thid);
	HANDLE thw2 = (HANDLE)_beginthreadex(NULL,0,Writer2,NULL,0,&thid);
	Sleep(10000); //执行时间
	CloseHandle(thw1);
	CloseHandle(thw2);
	CloseHandle(g_mutext);
	return 0;
}



参考

http://blog.csdn.net/ccing/article/details/6215998 

下次再讨论线程存贮和多线程日志库。











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值