【Windows编程】Windows多线程入门

01.文章目录

02.浅谈多线程

多线程在编程中有相当重要的地位,我们在实际开发时或者找工作面试时总能遇到多线程的问题,对多线程的理解程度从一个侧面反映了程序员的编程水平。

其实C++语言本身并没有提供多线程机制,但Windows系统为我们提供了相关API,我们可以使用它们来进行多线程编程。本文就以实例的形式聊聊多线程编程的知识。

但是C++11之后,C++也有了自己的多线程库,后面再聊,先聊Windows提供的API

03.创建线程的API(单独聊)

C++代码:

//该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄。
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,  
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, 
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

参数说明:

  • IpThreadAttributes线程安全属性设置,一般设置为NULL;

  • dwStackSize 指定了线程的堆栈深度,一般设置为0,表示线程堆栈与创建它的线程相同

  • IpStartAddress 线程执行时的地址,即线程执行所在函数的地址 函数创建规定:DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。

  • IpParameter 指定了线程执行时传送给线程的32位参数,即线程函数的参数

  • dwCreationFlags 控制线程创建的附加标志,可以取两种值:如果该参数为0,线程在被创建后就会立即开始执行;
    如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;

  • lpThreadId:该参数返回所创建线程的ID;

ps:LPVOID是一个没有类型的指针,也就是说你可以将LPVOID类型的变量赋值给任意类型的指针,比如在参数传递时就可以把任意类型传递给一个LPVOID类型为参数的方法,
然后在方法内再将这个“任意类型”从传递时的“LPVOID类型”转换回来。

3.1线程ID

线程ID类型 DWORD
例如:DWORD n = GetCurrentThreadId(); //获取当前进程的ID
将n的地址传入线程创建函数即可。

04.线程相关API

这里主要聊三个常用的API:
1.线程悬挂和恢复
2.设置线程优先级
3.线程退出

4.1 线程悬挂和恢复

创建新的线程后,该线程就开始启动执行。但如果在dwCreationFlags中使用了CREATE_SUSPENDED特性,线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程。

//该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。
DWORD SuspendThread(HANDLE hThread);

结束挂起状态:

//该函数用于结束线程的挂起状态,执行线程。
DWORD ResumeThread(HANDLE hThread);
4.2 线程优先级

设置线程的优先级和得到线程的优先级,即CPU把时间片段给谁

//调用该函数得到线程优先权。
int GetThreadPriority(HANDLE hThread);
//调用该函数来设置线程的优先权。	
BOOL SetThreadPriority(HANDLE hThread,int nPriority);
4.3线程退出

当调用线程的函数返回后,线程自动终止。

//如果需要在线程的执行过程中终止则可调用函数:
void ExitThread(DWORD dwExitCode);
//如果在线程的外面终止线程,则可调用下面的函数:
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

05.线程注意与拓展

5.1 线程注意事项
  • TerminateThread函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,一般情况下,建议不要使用该函数。
  • 如果要终止的线程是进程内的最后一个线程,则线程被终止后相应的进程也应终止。
  • 释放资源后,将线程HANDLE置成NULL。
  • 使用TerminateThread后,需调用CloseHandle( )函数释放线程所占用的堆栈。
5.2 拓展——进程间通信
5.2.1 全局变量方式
  1. 进程和线程共享全局变量,可利用该全局变量达到通信的目的。
  2. 将进程的HADNLE作为参数传递给线程函数,然后线程可根据此HANDLE对进程的变量进行操作。
5.2.2 消息通信方式

消息通信方式肯定是借助Windows提供的API了:
1.BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
2.BOOL PostThreadMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

显然它们发送到的对象不同:PostMessage发消息给窗口,PostThreadMessage发消息给线程。且当PostThreadMessage的hWnd = NULL时,表明该消息传递给当前线程本身。

BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam =0 );
返回值如果公布了消息,则返回非零值;否则返回0。不管对方有没有收到,直接返回。

参数说明:
message:指定了要公布的消息。
wParam:指定了附加的消息信息。这个参数的内容依赖于要公布的消息。
lParam:指定了附加的消息信息。这个参数的内容依赖于要公布的消息。

说明:这个函数将一个消息放入窗口的消息队列,然后直接返回,并不等待对应的窗口处理消息。消息队列中的消息是通过调用Windows的GetMessage或PeekMessage函数来获得的。可以通过Windows的PostMessage函数来访问其它应用程序。

BOOL PostThreadMessage( UINT message , WPARAM wParam, LPARAMlParam );
返回值如果成功,则返回非零值;否则返回0。

参数说明:
message:用户自定义消息的ID。
wParam:第一个消息参数。
lParam:第二个消息参数。

说明:调用这个函数以向其它CWinThread对象发送一个用户自定义消息。发送的消息通过消息映射宏ON_THREAD_MESSAGE被映射到适当的消息处理函数。

注:CloseHandle是关闭线程句柄,用来释放线程资源的,不是终止线程的。关闭线程句柄只是释放句柄资源,新开启线程后,如果不再利用其句柄,应该关闭句柄,
释放系统资源。关闭线程句柄和线程的结束与否没有关系。如果主线程只想创建线程,而并不想之后再查询或操纵它,那么及时关闭句柄是个好习惯,免得当时没关,
以后又忘了,于是泄漏了系统的句柄资源(系统句柄总数是有限的)。

06.互斥量

互斥量是将资源互斥,即进程在某一相同的程序时,可以将某一程序片段设定为互斥量,线程不能同时访问此代码段,只有当前线程退出此互斥锁,另一线程才可以进入。

6.1 创建互斥量
HANDLE WINAPI CreateMutex(
       LPSECURITY_ATTRIBUTES lpMutexAttributes,        //线程安全相关的属性,常置为NULL
       BOOL                  bInitialOwner,            //创建Mutex时的当前线程是否拥有Mutex的所有权
       LPCTSTR               lpName                    //Mutex的名称
);

参数说明:

  • MutexAttributes:也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。
  • bInitialOwner:表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex
  • lpName:Mutex的名称
6.1 互斥量等待指定对象

WaitForSingleObject:等待一个指定的对象(如Mutex对象),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。

DWORD WINAPI WaitForSingleObject(
HANDLE hHandle,                             //要获取的锁的句柄
DWORD  dwMilliseconds                           //超时间隔
);

参数说明:

  • hHandle:要等待的指定对象的句柄。
  • dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。
6.3 释放互斥量
//说明:释放所拥有的互斥量锁对象,hMutex为释放的互斥量句柄
BOOL WINAPI ReleaseMutex(HANDLE hMutex);

参数说明:
hMutex:指定要释放的互斥量句柄名字。

07.多线程实例(多版本对照)

声明:此博客所有的实例都是C++版

7.1 多线程(不做处理)

这里我说明一下:我们手动创建的线程均是子线程,父线程既是创建他的进程,线程依赖于进程而存活。

#include <iostream>
#include <Windows.h> //头文件,用到的函数都出自这里面
using namespace std;

//子线程
DWORD WINAPI FUN(LPVOID lparameter)
{//这里的i跟下面的i是区分的,两个函数中的局部变量不冲突
	for(int i = 0; i < 10; i++)
	{
		cout << "This is child Thread" << endl;
	}
	return NULL;
}

int main()
{
	//创建线程
	HANDLE hThread = CreateThread(NULL,0,FUN,NULL,0,NULL); //如果想要线程ID,最后一个参数找个值来接
	CloseHandle(hThread);
	for(int i = 0; i < 10; i++)
	{
		cout << "This is main Thread" <<endl;
	}

	system("pause");
	return 0;
}

Result:
上面的执行结果是主线程打印1次,然后子线程打印10次,最后主线程打印剩下的。而且执行很快,看不清效果,我们用Sleep函数给个延迟。

7.2 多线程(Sleep版)
#include <iostream>
#include <Windows.h> //头文件,用到的函数都出自这里面
using namespace std;

//子线程
DWORD WINAPI FUN(LPVOID lparameter)
{//这里的i跟下面的i是区分的,两个函数中的局部变量不冲突
	for(int i = 0; i < 10; i++)
	{
		cout << "This is child Thread" << endl;
		Sleep(1000);
	}
	return NULL;
}

int main()
{
	//创建线程
	HANDLE hThread = CreateThread(NULL,0,FUN,NULL,0,NULL); //如果想要线程ID,最后一个参数找个值来接
	CloseHandle(hThread);
	for(int i = 0; i < 10; i++)
	{
		cout << "This is main Thread" <<endl;
		Sleep(1000);
	}

	system("pause");
	return 0;
}

Result:
这个版本虽然比上个版本好,但是,并没有改变根本,还是子线程跟主线程抢CPU资源,偶尔交替执行,而且,有时候打印完一句话之后本来应该换行,但是资源被抢走了,所以,没有换行,打印另外一个,但是偶尔又换两行。所以,问题并没有解决,但是我们先解决换行。

7.3 多线程(解决换行问题)
#include <iostream>
#include <Windows.h> //头文件,用到的函数都出自这里面
using namespace std;

//子线程
DWORD WINAPI FUN(LPVOID lparameter)
{//这里的i跟下面的i是区分的,两个函数中的局部变量不冲突
	for(int i = 0; i < 10; i++)
	{
		cout << "This is child Thread\n";
		Sleep(1000);
	}
	return NULL;
}

int main()
{
	//创建线程
	HANDLE hThread = CreateThread(NULL,0,FUN,NULL,0,NULL); //如果想要线程ID,最后一个参数找个值来接
	CloseHandle(hThread);
	for(int i = 0; i < 10; i++)
	{
		cout << "This is main Thread\n";
		Sleep(1000);
	}

	system("pause");
	return 0;
}

Result:
这个版本,我们用C语言常用的换行符 \n 来代替了endl,问题解决了,确实是按照我们想要的换行方式换行的。但是我们还是得解决根本问题——资源分配掌握在我们手里。
这个时候就要用到我们的互斥量了

7.4 多线程(互斥量版)
#include <iostream>
#include <Windows.h> //头文件,用到的函数都出自这里面
using namespace std;

//创建互斥量句柄
HANDLE hMutex;

//子线程
DWORD WINAPI FUN(LPVOID lparameter)
{//这里的i跟下面的i是区分的,两个函数中的局部变量不冲突
	for(int i = 0; i < 10; i++)
	{
		WaitForSingleObject(hMutex,INFINITE);
		cout << "This is child Thread\n";
		Sleep(1000);
		ReleaseMutex(hMutex);
	}
	return NULL;
}

int main()
{
	//创建线程
	HANDLE hThread = CreateThread(NULL,0,FUN,NULL,0,NULL); //如果想要线程ID,最后一个参数找个值来接
	hMutex = CreateMutex(NULL,FALSE,(LPCTSTR)"Mutex1");
	CloseHandle(hThread);
	for(int i = 0; i < 10; i++)
	{
		WaitForSingleObject(hMutex,INFINITE);
		cout << "This is main Thread\n";
		Sleep(1000);
		ReleaseMutex(hMutex);
	}

	system("pause");
	return 0;
}

Result:
这个版本就非常完美了,打印、换行都是按照我们想要的方式来的。但是这里提示一点,容易被忘记的一点。
创建互斥量的第二个参数,如果设置为TRUE,主线程就是所有者,就只会打印主线程,执行完主线程就结束了。所以如果想两个线程都能执行,仅仅只是互斥的效果,那么这里一定要设置为FALSE,上面的所有代码都是亲测,可以直接使用,下面再贴一下所有者的意思代码!

7.5 多线程(所有权阐释)
#include <iostream>
#include <Windows.h> //头文件,用到的函数都出自这里面
using namespace std;

//创建互斥量句柄
HANDLE hMutex;

//子线程
DWORD WINAPI FUN(LPVOID lparameter)
{//这里的i跟下面的i是区分的,两个函数中的局部变量不冲突
	for(int i = 0; i < 10; i++)
	{
		//WaitForSingleObject(hMutex,INFINITE); //取消子线程的加锁就行了
		ReleaseMutex(hMutex); //这个的位置下面上面都一样,但是逻辑上是上面
		cout << "This is child Thread\n";
		Sleep(1000);
	}
	return NULL;
}

int main()
{
	//创建线程
	HANDLE hThread = CreateThread(NULL,0,FUN,NULL,0,NULL); //如果想要线程ID,最后一个参数找个值来接
	hMutex = CreateMutex(NULL,FALSE,(LPCTSTR)"Mutex1");
	CloseHandle(hThread);
	for(int i = 0; i < 10; i++)
	{
		WaitForSingleObject(hMutex,INFINITE);
		cout << "This is main Thread\n";
		Sleep(1000);
		//ReleaseMutex(hMutex);
	}

	system("pause");
	return 0;
}

08.小结

关于多线程,说实话,我也不是很明白,但是上面这些是我目前消化了的知识,是没有问题的。
今天就浅谈一下多线程的创建、互斥量在多线程中的作用。
互斥量的使用场景一定要是多个线程使用同一资源时才有意义,当然,解决多线程同步的方法还有很多,比如我知道的还有原子类也可以,关于原子类就后面再详谈吧。

在这里插入图片描述

如果上面的内容对你有用,可以点个收藏留条回来的路,哈哈哈。

版权声明:转载请注明出处,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cain Xcy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值