c++ 学习笔记(27)多线程的实现

本文通过实例演示了多线程编程的基本概念和技术细节,包括线程创建、等待、同步和互斥等核心机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

根据一个最详细的多线程的学习笔记,做出自己的理解:http://blog.youkuaiyun.com/morewindows/article/details/7442333

1. 首先是一个最简单的开始:

这个程序的主线程会创建了一个子线程并等待其运行完毕,子线程就输出它的线程ID号然后输出一句经典名言——Hello World。

#include "iostream"
#include "windows.h"
using namespace std;

//子线程函数
DWORD WINAPI ThreadFun(LPVOID pM)
{
	printf("子线程的ID号为: %d \n 子线程输出Hello World\n", GetCurrentThreadId());
	return 0;
}

//主函数, 所谓主函数其实就是主线程执行的函数。
int main()
{
	printf(" so easy for 多线程:)\n");
	
	HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
	WaitForSingleObject(handle, INFINITE);
	return 0;
}



其中每一次的ID会变化的。

第一个 CreateThread

函数功能:创建线程

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

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

函数返回值:

成功返回新线程的句柄,失败返回NULL


第二个 WaitForSingleObject

函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。

函数原形:

DWORDWINAPIWaitForSingleObject(

  HANDLEhHandle,

  DWORDdwMilliseconds

);

函数说明:

第一个参数为要等待的内核对象。

第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。

因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。


函数返回值:

在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED


CreateThread  由多个线程访问修改导致的数据覆盖问题。

其中:DWORD WINAPI ThreadFun(void *pPM) 
CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);  

是相互配合使用的,而对于_beginthreadex为:

unsigned int __stdcall ThreadFun(PVOID pM)

HANDLE handle =(HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);


2. 解决一个问题 读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作

多个线程存在问题这样由于线程执行的并发性,很可能线程A执行到第二句时,线程B开始执行,线程B将原来的值又写入寄存器eax中,这样线程A所主要计算的值就被线程B修改了。这样执行下来,结果是不可预知的——可能会出现50,可能小于50

利用新的函数

1.增减操作

LONG__cdeclInterlockedIncrement(LONG volatile* Addend);

LONG__cdeclInterlockedDecrement(LONG volatile* Addend);

返回变量值运算后与0比较的值,等于0返回0,大于0返回正数,小于0返回负数。

LONG__cdeclInterlockedAdd(LONG volatile* AddendLONGValue);

返回运算后的值,注意!加个负数就是减。

 

2.赋值操作

LONG__cdeclInterlockedExchange(LONG volatile* TargetLONGValue);

Value就是新值,函数会返回原先的值。

这样可以打印成功,都是50. 但是当递增变为100时,结果出现了问题:

#include "iostream"
#include "windows.h"
#include "process.h"
using namespace std;

volatile long g_nLoginCount;
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
	Sleep(100);
	//printf("子线程的ID号为: %d \n 子线程输出Hello World\n", GetCurrentThreadId());
	InterlockedIncrement((LPLONG)&g_nLoginCount);
	Sleep(50);
	return 0;
}

//主函数, 所谓主函数其实就是主线程执行的函数。
int main()
{
	printf(" so easy for 多线程:)\n");

	const DWORD Thead_Num = 100;   //change to 100
	HANDLE handle[Thead_Num];
	int _new = 20;
	
	while(_new--)
	{
		g_nLoginCount = 0;
		for( int i = 0; i < Thead_Num; i++)
			 handle[i] =(HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
		
		WaitForMultipleObjects(Thead_Num, handle, TRUE, INFINITE);  //单个的等待,可以多个
		cout << "the note:" << g_nLoginCount << endl;
	}
	return 0;
}



3.这个问题涉及到线程的同步和互斥,是一道非常有代表性的多线程同步问题,如果能将这个问题搞清楚,那么对多线程同步也就打下了良好的基础。

消除:关键段

函数功能:进入关键区域

函数原型:

void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

函数说明:系统保证各线程互斥的进入关键区域。

***然后在经典多线程问题中设置二个关键区域。一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时

关键段可以用于线程间的互斥,但不可以用于同步,

利用event可以进行同步:

//经典线程同步互斥问题
#include "iostream"
#include "windows.h"
#include "process.h"
using namespace std;
//设置两个关键区域,第一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时。

long g_nNum;  //全局资源
unsigned int __stdcall Fun(void *pPM);    //线程函数
const int Thread_Num = 10;  //子线程个数

//关键段变量的申请   //用于各子线程间的互斥
CRITICAL_SECTION g_csTreadCode;

//event  //用于同步
HANDLE g_hThreadEvent;


int main()
{
	g_nNum = 0;
	HANDLE handle[Thread_Num];

	g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  //第二个参数FALSE自动置位
	//关键段初始化
	//InitializeCriticalSectionAndSpinCount(&g_csTreadParameter, 4000);
	InitializeCriticalSectionAndSpinCount(&g_csTreadCode, 4000);

	int i = 0;
	while(i < Thread_Num)
	{
		//EnterCriticalSection(&g_csTreadParameter);   //进入子线程序号关键区域
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);    //&i Argument list to be passed to a new thread or NULL.
		WaitForSingleObject(g_hThreadEvent, INFINITE);
		i++;
	}
	//保证子线程已经全部运行结束
	WaitForMultipleObjects(Thread_Num, handle, TRUE, INFINITE);

	//DeleteCriticalSection(&g_csTreadParameter);
	CloseHandle(g_hThreadEvent);
	DeleteCriticalSection(&g_csTreadCode);
	return 0;
}

unsigned int __stdcall Fun(void *pPM)
{
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
	int nTreadNum = *(int *)pPM;
	//LeaveCriticalSection(&g_csTreadParameter); //离开子线程序号关键区域
	SetEvent(g_hThreadEvent);  //触发事件

	Sleep(50);

	EnterCriticalSection(&g_csTreadCode);   //进入格子线程互斥区域
	g_nNum++;   //处理全局资源
	Sleep(0);
	cout << "number of thread is:" << nTreadNum << "处理全局资源" << g_nNum << endl;
	LeaveCriticalSection(&g_csTreadCode);     //离开各子线程互斥区域

	return 0;
}


//提高关键段的性能,旋转锁
//初始化关键段,并设置旋转次数。
//BOOL InitializeCriticalSectionAndSpinCount()

利用时间进行实现同步,同步是多个线程同时,而互斥是多个线程不能同时访问同一资源。并且PulseEvent将实现一个事件脉冲:

//PluseEvent()
#include "iostream"
#include "windows.h"
#include "process.h"
#include "conio.h"
using namespace std;

HANDLE g_nThreadEvent;

//快线程
unsigned int __stdcall FastThreadFun(void *pPM)
{
	Sleep(10);
	cout << "启动:" << (PSTR)pPM;
	WaitForSingleObject(g_nThreadEvent, INFINITE);
	cout << "wait the event for 触发,结束:" << (PSTR)pPM << endl;

	return 0;
}

//慢线程
unsigned int __stdcall SlowThreadFun(void *pPM)
{
	Sleep(100);
	cout << "启动:" << (PSTR)pPM;
	WaitForSingleObject(g_nThreadEvent, INFINITE);
	cout << "wait the event for 触发,结束:" << (PSTR)pPM << endl;

	return 0;
}

int main()
{
	BOOL bManualReset = TRUE;  //手动还是自动
	
	g_nThreadEvent = CreateEvent(NULL, bManualReset, FALSE, NULL);
	if(bManualReset == TRUE)
	{
		cout << "手动置位事件";
	}
	else 
		cout << "自动置位事件";

	char fastThreadName[5][20] = {"fast1:", "fast2:", "fast3:", "fast4:", "fast5:"};
	char slowThreadName[2][20] = {"slow1:", "slow2:"};

	int i = 0;
	for(i = 0; i < 5; i++)
		_beginthreadex(NULL, 0, FastThreadFun, fastThreadName[i], 0, NULL);
	for(i = 0; i < 2; i++)
		_beginthreadex(NULL, 0, SlowThreadFun, slowThreadName[i], 0, NULL);

	Sleep(50);
	cout << "现在触发一个子线程:" <<endl;
	PulseEvent(g_nThreadEvent);

	Sleep(3000);
	cout << "主线程结束" << endl;
	CloseHandle(g_nThreadEvent);
	
	return 0;
}


4.互斥量的实现:

//PluseEvent()
#include "iostream"
#include "windows.h"
#include "process.h"
#include "conio.h"
using namespace std;

HANDLE g_nThreadMutex;
CRITICAL_SECTION g_csThreadCode;
long g_nNum;
const int Thread_Num = 10;

//快线程
unsigned int __stdcall FastThreadFun(void *pPM)
{
	Sleep(10);
	int nThreadNum = *(int *)pPM;
	ReleaseMutex(g_nThreadMutex);
	
	EnterCriticalSection(&g_csThreadCode);   //进入格子线程互斥区域
	g_nNum++;
	//WaitForSingleObject(g_nThreadEvent, INFINITE);
	cout << "wait the event for 触发,结束:" << nThreadNum << endl;
	cout << g_nNum << endl;
	LeaveCriticalSection(&g_csThreadCode);     //离开各子线程互斥区域

	return 0;
}

int main()
{
	HANDLE handle[Thread_Num];

	InitializeCriticalSectionAndSpinCount(&g_csThreadCode, 4000);
	g_nThreadMutex = CreateMutex(NULL, FALSE, NULL);

	int i = 0;
	while(i < Thread_Num)
	{
		//OpenMutex(MUTEX_ALL_ACCESS, TRUE, NULL);
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, FastThreadFun, &i, 0, NULL);
		WaitForSingleObject(g_nThreadMutex, INFINITE);
		i++;
	}

	WaitForMultipleObjects(Thread_Num, handle, TRUE, INFINITE);

	CloseHandle(g_nThreadMutex);
	DeleteCriticalSection(&g_csThreadCode);
	for(i = 0; i < Thread_Num; i++)
		CloseHandle(handle[i]);
	return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值