欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Win32、MFC多线程小结

程序员文章站 2023-12-31 19:20:22
...

1. 使用Win32SDK实现多线程


1.创建线程

//Win32MultiThread_Li5.1.cpp : 定义控制台应用程序的入口点。

//例5.1一个简单的线程函数定义及线程创建的例子:使用CreateThread()

// Win32MultiThread_Li5.1.cpp : 定义控制台应用程序的入口点。
//例5.1一个简单的线程函数定义及线程创建的例子:使用CreateThread()
#include "stdafx.h"
#include"windows.h"
#include<iostream>
using namespace std;

//定义线程函数,该线程函数无参数
void ThreadFun1()
{
	for (int i = 1; i < 5; i++)
	{
		Sleep(1000);
		cout << i << ",This is subThread1" << endl;
	}
}

HANDLE hThread1;//线程句柄
DWORD ThreadID1;//线程ID



int _tmain(int argc, _TCHAR* argv[])
{
	//创建线程,由于线程无参数,所以CreateThread()函数的第四个参数为NULL
	/*
	WINBASEAPI
	_Ret_maybenull_
	HANDLE
	WINAPI
	CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );
	*/
	hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun1, NULL, 0, &ThreadID1);
	for (int j = 1; j < 10; j++)
	{
		Sleep(1000);
		cout << j << ",This is MainThread!" << endl;
	}
	system("pause");
	return 0;
}

/*

1,This is subThread1
1,This is MainThread!
2,This is subThread1
2,This is MainThread!
3,This is subThread1
3,This is MainThread!
44,This is subThread1
,This is MainThread!
5,This is MainThread!
6,This is MainThread!
7,This is MainThread!
8,This is MainThread!
9,This is MainThread!
请按任意键继续. . .

*/

2.向线程函数传递参数

// Win32MultiThrea_Li52.cpp : 定义控制台应用程序的入口点。
//这个例子主要说明如何在主线程向子线程间传递数据!
/*Win32程序的开头都可看到:
#include <windows.h>
WINDOWS.H是一个最重要的头文件,它包含了其他Windows头文件,
这些头文件的某些也包含了其他头文件。
这些头文件中最重要的和最基本的是:
WINDEF.H 基本数据类型定义。
WINNT.H 支持Unicode的类型定义。
WINBASE.H Kernel(内核)函数。
WINUSER.H 用户界面函数。
WINGDI.H 图形设备接口函数。
这些头文件定义了Windows的所有资料型态、函数调用、
资料结构和常数识别字,它们是Windows文件中的一个重要部分。
*/
#include "stdafx.h"
#include"windows.h"
#include"atlstr.h"
#include<iostream>
using namespace std;

struct Student{
	int ID;
	char name[20];
};

int ThreadFun0(LPVOID lpParam)
{
	for (int i = 1; i < 5; i++)
	{
		Sleep(1000);
		cout << "I am thread0,the number main thread given me is" << (int)lpParam << endl;
	}
	return 0;
}

int ThreadFun1(LPVOID lpParam)
{
	char* p = (char*)lpParam;
	Sleep(1000);
	cout << "This is Thread1,the string main thread given me is :" << *p << endl;
	return 0;
}

int ThreadFun2(LPVOID lpParam)
{
	CString* p = (CString*)lpParam;
	Sleep(1000);
	cout << "This is Thread2,the string main thread given me is:" << *p << endl;
	return 0;
}

int ThreadFun3(LPVOID lpParam)
{
	Student* p = (Student*)lpParam;
	Sleep(1000);
	cout << "This is Thread3,the struct main thread given me is:the student's id:" << p->ID << ",the student name is " << p->name << endl;
	return 0;
}

HANDLE hThread0, hThread1, hThread2, hThread3;
DWORD ThreadID0, ThreadID1, ThreadID2, ThreadID3;

int _tmain(int argc, _TCHAR* argv[])
{
	int a = 888;
	char s1[] = "ABCDEF";
	CString s2("abcdef");
	Student stu{ 20130097, "小明" };
	cout << "This is MainThread!" << endl;
	hThread0 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun0, (void*)a, 0, &ThreadID0);
	hThread0 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun1, s1, 0, &ThreadID1);
	hThread0 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun2, (void*)&s2, 0, &ThreadID2);
	hThread0 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun3, (void*)&stu, 0, &ThreadID3);
	Sleep(1000);//主线程阻塞1秒,
	system("pause");
	return 0;
}

/*
因为这四个线程并没有做顺序上的同步,所以,输出是混乱的!:
This is MainThread!
This is Thread1,the string main thread given me is :AThis is Thread2,the string main thread given me is:00FC6BA8
I am thread0,the number main thread given me is
888This is Thread3,the struct main thread given me is:t请按任意键继续. . . he student's id:20130097,the student name is 小明

I am thread0,the number main thread given me is888
I am thread0,the number main thread given me is888
I am thread0,the number main thread given me is888
*/

3.控制线程悬挂、重启、退出


// Win32MultiThreadControl.cpp : 定义控制台应用程序的入口点。
//Win32 :线程的控制:创建、挂起、恢复、退出!
//使用CreateThread创建两个线程,在这两个线程中Sleep一段时间,主线程
//通过GetExitCodeThread来判断两个线程是否结束运行。
#include "stdafx.h"
#include"windows.h"
#include<iostream>
using namespace std;

DWORD WINAPI ThreadFunc(LPVOID n)
{
	int m = (DWORD)n;
	Sleep(10 * (5 - m));
	return m * 10;
}

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hThread1, hThread2;
	DWORD exitCode1 = 0, exitCode2 = 0;
	DWORD ThreadID1, ThreadID2;
	hThread1 = CreateThread(NULL, 0, ThreadFunc, (LPVOID)1, 0, &ThreadID1);
	//CreateThread()在调用进程的空间中创建一个新线程,并返回新线程句柄
	if (hThread1)cout << "Thread 1 launched!" << endl;//CreateThread()若创建线程成功则返回线程句柄,否则,返回NULL
	hThread2 = CreateThread(NULL, 0, ThreadFunc, (LPVOID)2, CREATE_SUSPENDED, &ThreadID2);
	if (hThread2)
	{
		ResumeThread(hThread2);
		cout << "Thread2 launched!" << endl;
	}

	for (;;)
	{
		GetExitCodeThread(hThread1, &exitCode1);
		GetExitCodeThread(hThread2, &exitCode2);
		if (exitCode1 == STILL_ACTIVE)
		{
			cout << "Thread 1 is still active!" << endl;
		}
		else {
			cout << "Thread 1's exitCode is" << exitCode1 << endl;
		}
		if (exitCode2 == STILL_ACTIVE)
		{
			cout << "Thread 2 is still active!" << endl;
		}
		else {
			cout << "Thread 2's exitCode is" << exitCode1 << endl;
		}
		if (exitCode1 != STILL_ACTIVE&&exitCode2 != STILL_ACTIVE)
		{
			break;
		}
	}
	system("pause");
	return 0;
}

/*
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2 is still active!
Thread 1 is still active!
Thread 2's exitCode is259
Thread 1 is still active!
Thread 2's exitCode is259
Thread 1 is still active!
Thread 2's exitCode is259
Thread 1 is still active!
Thread 2's exitCode is259
Thread 1's exitCode is10
Thread 2's exitCode is10
请按任意键继续. . .

*/

2.C++运行库中的多线程


标准C运行时库是1970年问世的,当时没有多线程的概念。

Visual C++提供了两个版本的C运行时库:单线程版本、多线程版本;

多线程库相比单线程库有两点重大区别:

1)      类似errno的全局变量,每个线程都单独设置一个,这样可以从每个线程中获取正确的错误信息。

2)      多线程库中提供的数据结构以同步机制加以保护。这样可以避免访问时冲突。


C++六个运行库(在头文件:process.h中)


C运行库

库文件

Single thread(static link)

libc.lib

Debug signle thread(static link)

Libdc.lib

MultiThread(static link)

libcmt.lib

Debug multiThread(static link)

libcmtd.lib

MultiThread(dynamic link)

msvert.lib

Debug multiThread(dynamic link)

msvertd.lib

process.h文件中定义:

下面是两个创建线程的函数:

_CRTIMPuintptr_t__cdecl _beginthread (_In_void (__cdecl * _StartAddress) (void *),

        _In_ unsigned _StackSize,_In_opt_ void * _ArgList);

 

_CRTIMPuintptr_t__cdecl _beginthreadex(_In_opt_void * _Security, _In_unsigned _StackSize,

        _In_unsigned (__stdcall * _StartAddress) (void *), _In_opt_void * _ArgList,

        _In_unsigned _InitFlag, _Out_opt_unsigned * _ThrdAddr);

以上两个函数创建的线程在线程函数内部退出线程的方法分别对应:

_CRTIMPvoid__cdecl _endthread(void);

_CRTIMPvoid__cdecl _endthreadex(_In_unsigned _Retval);// _Retval为退出代码

 

3.MFC类库多线程


工作者线程(WorkerThread):没有消息循环

用户界面线程(User-interfaceThread):有自己的消息循环和消息队列

参考文献:马石安.Visual C++程序设计与应用教程(第三版)[M]. 清华大学出版社, 2017.

相关代码:链接:https://pan.baidu.com/s/136JnCPQ-RhSG75RB9doVhA密码:weyf

3.1创建工作者线程


步骤:

1)      定义线程函数:

UINT FunctionName(LPVOID pParam);

2)      启动线程

CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,

    int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0,

    DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

参考[2]例10.2


3.2创建用户界面线程


步骤:

1)      从CWinThread类派生一个新的线程类,并重写派生类的InitInsatnce()、ExitInstance()及Run()函数

2)      启动线程

CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass,

    int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0,

    DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

// AfxBeginThread(RUNTIME_CLASS(class_name));

代表进程主线程的CWinApp类就是CWinThread的派生类,实际上CWinApp类所启动的主线程就是一个用户界面线程!

所以说,从CWinThread类派生一个新的线程类产生的新线程窗口是和主线程的CWinApp类并列的!子线程窗口的关闭不会对主线程窗口造成影响,但主线程的窗口强行关闭时,子线程的窗口也会被关闭,这种情况属于用户界面线程非正常退出,造成内存泄漏。

3.3线程的控制


线程的终止:

voidAFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete = TRUE);

对于工作者线程的线程函数执行一个return语句或者调用了AfxEndThread()成员函数,该工作者线程就终止。

对于用户界面线程,当收到一个WM_QUIT消息时,或者该线程中的某个函数调用了AfxEndThread()成员函数时,该线程终止。

 

悬挂和恢复线程:

WINBASEAPIDWORDWINAPI SuspendThread(

   _In_HANDLE hThread

    );

 

WINBASEAPIDWORDWINAPI ResumeThread(

    _In_HANDLE hThread

    );


3.4线程的优先级


    int GetThreadPriority();

BOOL SetThreadPriority(int nPriority);


3.5线程间通信


1)    使用全局变量进行通信

对于标准类型的全局变量,使用volatile修饰!

2)    MFC:使用自定义消息进行通信:

可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的,一个线程向另一个线程发生消息是通过操作系统实现的。

利于Windows操作系统的消息驱动机制,当一个线程发送一条消息时,操作系统首先收到该消息,然后把该消息转发给目标线程,接受消息的线程必须建立了消息循环(得时用户界面线程)。

利用Windows消息来进行通信,首先必须定义一个自定义消息,然后,需要时在一个线程中调用全局函数:: PostMessage()向另一个线程发送自定义消息。

WINUSERAPIBOOLWINAPI PostMessageW(

    _In_opt_HWND hWnd,

    _In_UINT Msg,

    _In_WPARAM wParam,

  _In_LPARAM lParam);

#ifdefUNICODE

#definePostMessage  PostMessageW

#else

#define PostMessage  PostMessageA

#endif// !UNICODE

hWnd发送消息的窗口的句柄,Msg为消息IDwParam、lParam为消息相关参数。

XXXView.h文件自定义消息:

#defineWM_CALCULATE  WM_USER + 100//自定义消息

XXXView.h文件的消息定义部分加上如下代码:

afx_msgLONG OnThreadEnd(WPARAM wParam, LPARAM lParam);//自定义消息的消息映射函数声明

XXXView.cpp文件的消息映射部分加上如下代码:

ON_MESSAGE(WM_CALCULATE, OnThreadEnd)

然后:

UINT CalculatePrime(LPVOIDpParam)//LPVOID=void* 可以将其理解为long型的指针,指向void型。

{

//执行计算素数个数是程序代码。

    //向主线程发送WM_CALCULATE消息(自定义Windows消息)

    ::PostMessage((HWND)pParam, WM_CALCULATE, n, 0);

}

XXXView.cpp

在主线程窗口中添加消息处理函数的定义:

//自定义消息的处理函数

LONGCMFCInterThreadCommunicationView::OnThreadEnd(WPARAMwParam, LPARAMlParam)

{

    CString str;

    str.Format(_T("The prime Numbers from 1 to 1000000 is %d."), n);

    AfxMessageBox(str);

    return 0;

}

这里的发送消息更像是做了同步,当子线程计算素数的程序代码执行完毕后就发送消息给主线程,主线程接收到消息后就在消息处理函数里处理该消息。


3.6MFC 线程间的同步


使得隶属于同一个进程的多个线程协调一致地工作称为线程的同步。

常用的同步对象有临界区(Critical Section)、互斥(Mutex)、信号量(Semaphore)和事件(Event)。

MFC提供了同步类和同步辅助类,这些类要含义头文件afxmt.h

表1MFC支持多线程同步的同步类


类名

说明

基类CSyncObject

同步对象的基类,抽象类,为Win32中的同步对象提供通用性能

临界区类CCriticalSection

在同一时间内仅有一个线程被允许修改数据或使用某些其他控制资源,用于保护共享资源

互斥类CMutex

有多个进程同时存取相应资源时使用,用于保护共享资源

信号类CSemaphore

一个应用允许同时有多个线程访问相应资源时使用,主要用于资源计数

事件类CEvent

某个线程必须等待某些事情发生以后才能存取相应资源时使用,协调多个线程间的动作

同步辅助类CSingleLock、CMultiLock

用于在一个多线程程序中控制对资源的访问,当在同一个时间只需等待一个同步化对象时使用CSingleLock类,否则使用CMultiLock


3.6.1.使用CCriticalSection


使用CCriticalSection对象对共享资源的读写都要进行加锁就OK:

CCriticalSection ctitical_section;

ctitical_section.Lock();

//读/写共享数据

ctitical_section.Unlock();


3.6.2.使用事件对象


Win32、MFC多线程小结

1.应用程序中如果要使用CEvent类进行线程同步,应先定义事件对象,然后在需要等待事件的线程中调用Lock()函数来监测有无事件。对于发生事件的线程,则调用SetEvent()和ResetEvent()来设置事件的状态。(自动事件不需要调用ResetEvent()函数)


2.使用事件对象实现线程通信:



// CEvent.cpp : 定义控制台应用程序的入口点。
//下面程序定义了两个事件对象:CEvent ThreadBegin, ThreadEnd;用于主线程控制子线程的运行。
//主线程使用CEvevt()类的成员函数SetEvent()使得事件状态变为有信号状态、子线程则调用WaitForSingleObject()
//来监视等待事件对象的状态由"无信号"变为"有信号";
#include "stdafx.h"
#include"afxmt.h"
/*在属性-常规-MFC的使用中设置一下使用DLL,就可以在控制台程序中使用MFC提供的类了!,不一定非要用MFC框架!*/

#include<iostream>
using namespace std;

CEvent ThreadBegin, ThreadEnd;

UINT ThreadProc(LPVOID param)
{
	WaitForSingleObject(ThreadBegin.m_hObject, INFINITE);
	cout << "Thread Activated!" << endl;
	BOOL KeepRunning = true;
	while (KeepRunning)
	{
		int Result = WaitForSingleObject(ThreadEnd.m_hObject, 0);
		if (Result == WAIT_OBJECT_0)
		{
			KeepRunning = false;
		}
		cout << "Thread Stopped!" << endl;
		return 0;
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	DWORD ThID;
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, &ThID);
	ThreadBegin.SetEvent();
	Sleep(3000);
	ThreadEnd.SetEvent();
	Sleep(2000);
	cout << "MainThread Exit!" << endl;
	system("pause");
	return 0;
}


参考文献

[1] 马石安. Visual C++程序设计与应用教程(第三版)[M]. 清华大学出版社, 2017.

[2] 杨传栋, 张焕远. Windows网络编程基础教程[M]. 清华大学出版社, 2015.

[3]参考教程:链接:https://pan.baidu.com/s/1S3IoIIJ3Wb90MrLFVlp8Og密码:li3o

[4]MFC多线程参考教程:链接:https://pan.baidu.com/s/11kRIwwOZTPMdJJuf_x_oAA密码:no5m












上一篇:

下一篇: