windows 线程核心内容

线程
线程由2部分组成:线程的内核对象(包括线程上下文(context)即cpu寄存器(最重要的寄存器是栈指针寄存器和指令指针寄存区)、主要作用存放线程统计信息)和 线程栈(维护线程执行所需的所有函数参数和局部变量)

假如一个进程由2个以上的线程运行,这些线程共享同一地址空间,共享内核对象句柄(因为句柄表是针对每一个进程的)

windows系统是抢占式操作系统,无法保证线程总在运行,线程会获得整个处理器,系统将不允许运行其他线程。也无法保证自己的线程在某个时间段开始运行。
线程创建
新线程与负责创建的那个线程在相同的进程上下文中运行,因此新线程可以访问进程内核对象的所有句柄,进程的所有内存以及同一进程中其他所有线程的栈(技术上线程栈数据可以相互访问,但基本不会这样操作,就相当于两个函数的局部变量一样)。所以多个线程使用同时访问静态变量和全局变量实现,但要注意同步问题,可以使用互斥锁等技术实现。
createthread 预定空间的大小要么由stack链接器开关指定,可见于项目属性配置中(具体为:在工程上右键 -> 属性 -> 配置属性 -> 链接器 -> 系统 -> 堆栈保留大小堆栈提交大小)。要么由cbstacksize的值指定,取较大值的那个,默认是1M大小。去进一步了解因线程栈大小引起的问题,涉及到线程栈异常及其捕获。如果在线程中局部变量是使用大内存的栈空间,会出现栈异常崩溃。

 createthread 函数讲解

1.psa 一般设置为null ,表示使用默认的安全属性。

2.cbstackszie  默认一般是1M,在这里写0就是表示这意思。

3.pfnstartaddr 线程函数地址  //可以是全局函数或者类的静态函数

4.pvparam 把该参数传给线程函数,该参数可以是数值或结构

5.dwcreateflags 可以是0(表示创建之后立即调度) 可以是create_suspended 表示挂起暂停线程。

6.pdwthreadid :线程id

终止线程
1.线程函数返回  最好的办法
2.调用exitthread 只能自己杀死自己,不能杀死别的线程:操作系统清理该线程使用的所有操作系统资源,但c++资源(如c++类对象)不会被销毁。如线程栈的局部变量的析构函数就不会执行,引起内存泄漏。

3.terminatethread随便杀死一个线程,是异步函数。 也会导致c++资源(如c++类对象)不会被销毁

4.进程终止运行,如调用exitprocess 或 terminatethread 函数  也会导致c++资源(如c++类对象)不会被销毁

创建和销毁线程会浪费cpu的时间切换线程上下文也会消耗cpu.

测试过创建1万个线程,创建和销毁时需要消耗5%的cpu.

对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被 清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。

线程终止
1.线程拥有的用户对象(窗口和挂钩)句柄会被释放。
2.线程内核对象状态变成触发状态进程初始化时为未触发状态,进程终止时自动变已触发状态。
3.线程内核对象的使用计数递减1

 

//在类外声明的全局函数
DWORD WINAPI datathread(PVOID p);

DWORD WINAPI datathread(PVOID p)
{
	return 0;
}

在类内声明的静态函数
static DWORD WINAPI handlethread(PVOID p);

DWORD WINAPI CMainFrame::handlethread(PVOID p)
{
	return 0;
}



void CMainFrame::Ontest()
{
	DWORD threadid;
	CreateThread(NULL, 0, datathread, this, 0, &threadid);
	CreateThread(NULL, 0, handlethread, this, 0, &threadid);
}

相对于createthread 更应用_beginthreadex ,相对于_endthreadex更应用_endthreadex。

线程的状态

线程状态有5种状态:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。其中在阻塞状态中,有挂起(suspendthread)、睡眠(sleep)、等待(wait)三种方式。
挂起和睡眠是主动的,挂起恢复需要主动完成,睡眠恢复则是自动完成的,因为睡眠有一个睡眠时间,睡眠时间到则恢复到就绪态。而等待阻塞是被动的,是在等待某种事件或者资源的表现,一旦获得所需资源或者事件信息就自动回到就绪态。

线程的挂起和恢复

挂起(suspendthread)

线程的挂起操作实质上就是线程进入"非可执行"状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行。 线程挂起后,可以通过重新唤醒线程来使之恢复运行。

线程内核对象有一个挂起技术,并初始化为1(即设置了CREATE_SUSPENDED标记位).目的在于线程初始化需要时间,不想在线程还没有准备好就开始了。当CREATE_SUSPENDED标志时,线程于是挂起。如果没有,线程计数挂起递减为0.线程变成可调度。一个线程可以挂起多次,但也需要相同恢复的次数。任何线程都可以挂起另外一个线程,包括挂起自己。异步过程。调用suspendthread要小心,如果使用该方法将某个线程挂起,可能会使其他等待资源的线程死锁。避免使用这种办法。

DWORD WINAPI SuspendThread( _In_ HANDLE hThread );  //返回线程前一个挂起计数

恢复(resumethread):

一个线程多次调用suspendthread挂起,就必须相应调用多次resumethread恢复。线程不能自己恢复自己。

DWORD WINAPI ResumeThread(_In_ HANDLE hThread);//返回线程前一个挂起计数

while (ResumeThread(threadHandle) > 0);

sleep() 方法和 wait() 方法区别和共同点?
两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
两者都可以暂停线程的执行。
wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。

Sleep(0):时间片只能让给优先级相同或更高的线程,
SwitchToThread():只要有可调度线程,即便优先级较低,也会让其调度。

等待 wait、唤醒niotify/notifyAll    使用的是c++的办法  这个是主流办法

std::condition_variable condit;
std::mutex mt;
std::unique_lock<std::mutex> lock(mt);

std::unique_lock<std::mutex> lock(m_mutex);
cond.wait(lock);

std::unique_lock<std::mutex> lock(m_mutex);
cond.notify_all();

对应地,没有进程的直接挂起(suspendprocess)和恢复函数,只能通过遍历进程的所有线程,并挂起线程。

线程时间段 = CPU加载线程上下文的时间 + CPU执行时间 + CPU保存线程上下文的时间 (是更为细小的时间段,远小于进程时间段)

线程执行时间的计算办法如下 (windows不是实时操作系统)所以sleep 函数精度也是在15ms左右

1.gettickcount 或者 gettickcount64  精度15ms左右

2.getthreadtimes

3.queryperformancefrequency 和 queryperformancecounter 高精度

线程优先级
有6个进程优先级,有7个相对线程优先级:time-critical、highest、above normal、nomal、below normal、lowest、idle    对应有0-31的优先级值。由进程优先级和相对线程优先级同时确定线程优先级值   。
setthreadpriority 、Getthreadpriority 参数都是相对线程优先级
较高优先级的线程总是会抢占较低优先级的线程,无论较低优先级的线程是否正在执行。例如,如果有一个优先级为5的线程在运行,而系统确定有较高优先级的线程已准备好可以运行,它会立即暂停较低优先级的线程(即使后者的时间片还没有用完),并将CPU分配给较高优先级的线程,该线程获得一个完整的时间片

 

判断线程是否结束的方法

1.GetExitCodeThread 比较原始的方法了,调用 GetExitCodeThread,如果返回STILL_ACTIVE,那就是还在运行,否则,会返回退出代码. 

2.WaitForSingleObject 等待线程的内核对象被激发,线程退出的时候该内核对象就会被激发了 该方法为阻塞函数,会一直堵塞直至线程退出.

线程之间的通信

1.全局变量,多个线程同时访问同一个共享资源.通常我们会在这个全局变量前加上volatile声明,以防编译器对此变量进行优化。

volatile  BOOL  bStop  =  FALSE;
延伸:volatile
有些变量是用 volatile 关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中,而是直接读取内存的数据。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

多线程同时访问同一个全局变量,需要加锁进行互斥访问。如

 std::mutex m_mutex;

 std::unique_lock<std::mutex> lock(m_mutex);

2.Message消息机制
常用的Message通信的接口主要有两个:PostMessagePostThreadMessage
PostMessage为线程向主窗口发送消息,PostMessage是异步的.而PostThreadMessage是任意两个线程之间的通信接口。

注:使用这个方法时,目标线程必须已经有自己的消息队列。否则会返回ERROR_INVALID_THREAD_ID错误。可以用PeekMessage(不阻塞)给线程创建消息队列,或者是GetMessage(阻塞)。具体见下面代码

延伸:每个线程都有自己的消息队列,除了主线程外,其他的子线程都需要自己实现消息循环。具体如:

while (TRUE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		Sleep(100);
	}
线程同步

1.临界区关键段

2.事件量 

3.互斥量 

4.信号量

关键段属于用户模式,互斥量、事件量、信号量属于内核模式。

用户模式要比内核模式性能快。

内核模式能跨进程使用,用户模式不能。

用户模式下的线程同步:关键段(critical section),以原子方式进行操控,一般作为全局变量分配。只有windows才有,c++和linux没有这特性。
initializecriticalsection、entercriticalsection、leavecriticalsection、deletecriticalsection 
如果两个线程调用entercriticalsection,一个线程获准访问资源,另一线程被切换到等待状态,这样等待的线程就不会浪费cpu.但也意味着从用户模式切换到内核模式,开销变大,解决办法加上旋转锁。

内核对象下的线程同步

内核对象的状态:要么触发状态,要么未触发状态。

进程内核对象在创建时处于未触发状态。当进程终止时,内核对象变成触发状态。线程也是一样的。

等待函数:使一个线程自愿进入等待状态,直到指定的内核对象被触发为止。如有waitforsingleobject和waitformutipleobject函数。进入等待状态,cpu挂起,不占cpu资源。

waitforsingleobject:

被触发,返回WAIT_OBJECT_0 ;

超时,返回WAIT_TIMEOUT;

失败,返回 WAIT_FAILED ,可用GetLastError。

waitformutipleobject:参数bwaitall决定是只要有任意一个被触发,还是全部被触发才行。

如果bwaitall 为false 时,只要有任意一个被触发,就返回,返回值为WAIT_OBJECT_0和WAIT_OBJECT_0 + dwcount -1 之间的任意一个值。

如果bwaitall 为true 时,成功时,返回值为WAIT_OBJECT_0

超时,返回WAIT_TIMEOUT;

失败,返回 WAIT_FAILED ,可用GetLastError。

事件量

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性

BOOL bManualReset,// 复位方式

BOOL bInitialState,// 初始状态

LPCTSTR lpName // 对象名称

);

bManualReset 

true: 表示手动重置,事件被触发的时候,正在等待该事件的所有线程都变成可调度的状态。

         之后必须用ResetEvent函数来手工将事件的状态复原到无信号状态

false:表示自动重置,只有一个正在等待该事件的线程会变成可调度的状态。

           当一个线程等待到事件信号后系统会自动将事件状态复原为无信号状态。

bInitialState

指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。

参考:windows 内核对象事件量 CEvent_小飞侠hello的博客-CSDN博客

互斥量


包含一个使用计数,线程id,一个递归计数。

线程id:用来表示占用这个互斥量是哪个线程

递归计数:线程占用这个互斥量的次数,要是次数大于1的唯一办法是:

                线程试图访问一个未触发的互斥量对象,如果发现调用互斥量的线程的线程id和互斥量记录的线程id一致,即使是该互斥量尚未触发

互斥量的规则

1.线程id=0,即互斥量不被任何线程占用,处于触发状态。  相当于false

2线程id不为0,便是有一个线程在占用,处于未触发状态。 相当于true

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针

BOOL bInitialOwner, // 初始化互斥对象的所有者

LPCTSTRlpName // 指向互斥对象名的指针

);

bInitialOwner 

true  表示互斥锁创建出来后被当前线程持有 对象的线程id等于调用线程的线程id. 递归次数变成1. 处于未触发状态。

false 表示互斥锁创建出来后不被当前线程持有   线程id和递归计数为0,互斥量处于触发状态

互斥量必须结合等待函数一起使用。

当等待函数发现互斥量处于触发状态(即线程id=0),之后变回变成这样

1.线程能够执行下去。

2.互斥量的线程id变就成调用线程的线程id.

3.互斥量的递归次数就变成1.

4.互斥量状态变成未触发状态。

当等待函数发现互斥量处于未触发状态(即线程id不为0, 初始化时第二个变量为true),线程进入等待状态。

如果发现调用互斥量的线程的线程id和互斥量记录的线程id一致,会发生以下

1.线程执行下去

2.互斥量的递归计数+1

releasemutex 

释放互斥量,结果是互斥量的递归次数减1.如果递归次数变成0,函数就线程id设置为0,互斥量就变成触发状态了。

参考:windows 互斥量内核对象 Mutex_小飞侠hello的博客-CSDN博客_windows mutex

信号量

信号量内核对象:(semaphore)对资源进行计数:最大资源计数(表示信号量控制的最大资源的数目)、当前资源计数(表示信号量当前可用的资源数目)

信号量规则:1.如果当前资源计数大于0,信号量处于触发状态 2.如果当前资源计数等于0,那么信号量处于未触发状态
HANDLE CreateSemaphore(

    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD

    LONG lInitialCount, // initial count

    LONG lMaximumCount, // maximum count

    LPCT STRlpName// object name

);

CreateSemaphore() lInitialCount:表示一开始可以使用的资源数目,即当前资源计数;lMaximumCount:信号量对象可以处理的最大资源数量

WaitForSingleObject为等待函数,被用在所有的内核对象触发等待中。等待函数会检查信号量的当前资源使用计数,如果大于0,表示信号量处于触发状态,那么等待函数会把资源使用计数器减1,并让调用线程继续执行。如果等于0,表示信号量处于未触发状态,那么系统会让调用线程进入等待状态,直到被唤醒
BOOL ReleaseSemaphore(

HANDLE hSemaphore,

LONG lReleaseCount,

LPLONG lpPreviousCount

);

ReleaseSemaphore函数的作用是增加信号量的计数.即增加lReleaseCount个。

ReleaseSemaphore  lReleaseCount:释放自己使用的资源数目,加到信号量的当前资源计数上,通常会传1.   当一个线程使用完信号量对象控制的有限资源后,应该调用ReleaseSemaphore,释放使用的资源,使信号量对象的当前资源计数得到恢复。

参考;https://blog.csdn.net/baidu_16370559/article/details/103120762

线程死锁
产生死锁必须具备以下四个条件:
互斥条件:该资源任意一个时刻只由一个线程占用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何预防死锁? 破坏死锁的产生的必要条件即可:
破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
死锁的解决办法:使用raii技术。检测死锁工具有:windbg,lockcop.exe

//主线程向子线程发送消息

#define MY_MSG WM_USER+100

//主线程	
bool CExternalSort::ReadFile(int nThread /*= 1*/)
{
	for (int i = 0; i < nThread; i++)
	{
		DWORD dwThreadId;
		HANDLE HThread = CreateThread(NULL, 0, ReadFileThread, this, CREATE_SUSPENDED, &dwThreadId);
		ResumeThread(HThread);
		CloseHandle(HThread);
		Sleep(1000);
		PostThreadMessage(dwThreadId, MY_MSG,0,0);
	}
	
	return true;
}



//子线程
DWORD WINAPI ReadFileThread(LPVOID lp)
{
	MSG msg;
	while (TRUE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (msg.message == MY_MSG)
				break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		Sleep(100);
	}
	return 0;
}



DWORD WINAPI ReadFileThread(LPVOID lp)
{
	MSG msg;
	while (TRUE)
	{
		if (GetMessage(&msg, 0, 0, 0)) 
		{
			switch (msg.message)
			{
			case MY_MSG:
				break;
			}
		}
		Sleep(100);
		return 0;
	}
    return 0;
}

//主线程和子线程 同时发送消息

#define MY_MSG WM_USER+100
#define MSG_TFREAD_TO_MAIN WM_USER+200

bool CreateThreadMsg()
{
	DWORD dwThreadId;
	HANDLE HThread;

	DWORD dwMainID = GetCurrentThreadId();
	HThread = CreateThread(NULL, 0, MsgThread, &dwMainID, CREATE_SUSPENDED, &dwThreadId);
	ResumeThread(HThread);
	Sleep(1000);
	DWORD dwThreadId1 = GetThreadId(HThread);
	PostThreadMessage(dwThreadId, MY_MSG, 0, 0);
	qDebug() << "main thread send message!";
	MSG msg;
	while (TRUE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == MSG_TFREAD_TO_MAIN)
			{
				qDebug() << "main thread accept message!";
				break;
			}
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		Sleep(100);
	}
	qDebug() << "waiting MsgThread quit!";
	WaitForSingleObject(HThread, INFINITE);
	CloseHandle(HThread);
	qDebug() << "CreateThreadMsg quit!";

	return true;
}

DWORD WINAPI MsgThread(LPVOID lp)
{
	DWORD *pthread = (DWORD*)(lp);
	if (pthread == nullptr)
	{
		return 0;
	}
	MSG msg;
	while (TRUE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (msg.message == MY_MSG)
			{
				qDebug() << "MsgThread accept message!";
				PostThreadMessage(*pthread, MSG_TFREAD_TO_MAIN, 0, 0);
				qDebug() << "MsgThread send message!";
				break;
			}
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		Sleep(100);
	}

	Sleep(5000);
	qDebug() << "MsgThread quit!";

	return 0;
}
//事件量和WaitForSingleObject 结合使用
static HANDLE st_handleevent = CreateEvent(NULL, FALSE, FALSE, NULL);//自动重置无信号、初始无信号

bool CreateThreadEvent()
{
	DWORD dwThreadId;
	HANDLE HThread;

	DWORD dwMainID = GetCurrentThreadId();
	HThread = CreateThread(NULL, 0, EventThread, &dwMainID, CREATE_SUSPENDED, &dwThreadId);
	ResumeThread(HThread);
	Sleep(5000);
	SetEvent(st_handleevent);
	WaitForSingleObject(HThread, INFINITE);
	CloseHandle(HThread);
	qDebug() << "CreateThreadEvent quit!";

	return true;
}

DWORD WINAPI EventThread(LPVOID lp)
{
	while (WaitForSingleObject(st_handleevent,1000) != WAIT_OBJECT_0)
	{
		qDebug() << "EventThread is running!";
	}

	Sleep(2000);
	qDebug() << "EventThread quit!";

	return 0;
}
static HANDLE st_handleMutex = CreateMutex(NULL,TRUE,NULL);//TRUE 表示绑定当前创建的线程,未触发,递归次数变1

bool CreateThreadMutex()
{
	DWORD dwThreadId;
	HANDLE HThread;

	DWORD dwMainID = GetCurrentThreadId();
	HThread = CreateThread(NULL, 0, MutexThread, &dwMainID, CREATE_SUSPENDED, &dwThreadId);
	ResumeThread(HThread);
	Sleep(5000);
	ReleaseMutex(st_handleMutex);//变触发状态,递归次数减1.
	WaitForSingleObject(HThread, INFINITE);
	CloseHandle(HThread);
	
	qDebug() << "CreateThreadMutex quit!";
	return true;
}

DWORD WINAPI MutexThread(LPVOID lp)
{
	while (WaitForSingleObject(st_handleMutex, 1000) != WAIT_OBJECT_0)
	{
		qDebug() << "MutexThread is running!";
	}
	//要是等待函数发现互斥量处于触发状态,返回WAIT_OBJECT_0,那么互斥量变未触发状态,递归次数为1.
	Sleep(2000);
	qDebug() << "MutexThread quit!";

	return 0;
}


static HANDLE st_handleSemaphore = CreateSemaphore(NULL, 0,1, NULL);//第二参数为当前资源计数,为0表示信号量未触发状态 ,第3参数为最大资源数
bool CreateThreadSemaphore()
{
	DWORD dwThreadId;
	HANDLE HThread;

	DWORD dwMainID = GetCurrentThreadId();
	HThread = CreateThread(NULL, 0, SemaphoreThread, &dwMainID, CREATE_SUSPENDED, &dwThreadId);
	ResumeThread(HThread);
	Sleep(5000);
	ReleaseSemaphore(st_handleSemaphore,1,NULL);//增加当前资源计数,使其>0,变成触发状态。

	WaitForSingleObject(HThread, INFINITE);
	CloseHandle(HThread);

	qDebug() << "CreateThreadSemaphore quit!";
	return true;
}

DWORD WINAPI SemaphoreThread(LPVOID lp)
{
	while (WaitForSingleObject(st_handleSemaphore, 1000) != WAIT_OBJECT_0)
	{
		qDebug() << "SemaphoreThread is running!";
	}
	//要是等待函数发现信号量处于触发状态,返回WAIT_OBJECT_0,那么互斥量的可用资源计数减1.如果等于0,信号量处于未触发状态
	Sleep(2000);
	qDebug() << "SemaphoreThread quit!";

	return 0;
}

 PeekMessage和GetMessage函数的主要区别有:

1. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
2. 如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
3. GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。


我们也可以说,PeekMessage是一个具有线程异步行为的函数,不管消息队列中是否有消息,函数都会立即返回。而GetMessage则是一个具有线程同步行为的函数,如果消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。


如果消息队列中没有消息,PeekMessage总是能返回,这就相当于在执行一个循环,如果消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能因为消息队列为空而进入死循环。

   在windows中一个线程只有一个消息队列(PostMessage消息队列),非模态对话框的消息在主线程的消息循环中处理。
   对于模态对话框,其先禁止主窗口,然后自己建立一个消息循环进行消息处理,对话框结束后,停止对话框自己的消息循环,然后enable主窗口。
   所以,一个线程中只有一个消息队列(PostMessage消息队列),但是可能存在主线程消息循环外的其他局部消息循环,但是他们不会并行执行。
   所以一个进程如果存在多个线程,也就会有多个消息队列。
   虽然
   在同一个线程中,PostMessage发送消息时,消息要先放入系统消息队列中,系统会根据存放的消息,找到对应的线程(窗口、程序)的消息队列中,然后由GetMessage/PeekMessage提交给TranslateMessage。
   而SendMessage发送消息时,由USER模块调用目标窗口的处理函数处理消息,并将结果返回。
   不在同一个线程,基本都是用PostThreadMessage代替PostMessage。因为PostThreadMessage是直接指定线程ID来确定目标线程;
   而SendMessage发送消息到目标窗口所属的线程的消息队列中,然后发送消息的线程在UESR模块内监视和等待消息处理,直到目标窗口处理完返回。

线程安全


线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。所以线程安全指的是内存的安全。
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。

解决办法:加锁,如互斥锁、读写锁、条件变量、信号量

进程和线程的区别

1.根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。

2.从属关系不同:进程中包含了线程,线程属于进程。

3.开销不同:进程的创建、销毁和切换的开销都远大于线程。

4.拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。

5.控制和影响能力不同:子进程无法影响父进程,而子线程可以影响父线程,如果主线程发生异常会影响其所在进程和子线程。一个进程崩溃,不会对其他进程产生影响,而一个线程崩溃,会让同一进程内的所有其他线程也死掉

6.CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。

7.操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。

延伸:c++11 thread

1.创建线程 

thread 声明为变量,声明周期结束自动释放join

join 等待函数,阻塞。等待子线程返回  //必须调用,不然崩溃 。或者是使用detach进行分离


void t1()  //普通的函数,用来执行线程
{
    for (int i = 0; i < 10; ++i)
    {
        cout << "t1111\n";
        sleep(1);
    }
}
void t2()
{
    for (int i = 0; i < 20; ++i)
    {
        cout << "t22222\n";
        sleep(1);
    }
}
int main()
{
    thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())
    thread th2(t2);
 
    th1.join(); // 必须将线程join或者detach 等待子线程结束主进程才可以退出
    th2.join(); 
}

同步

std::mutex互斥量

lock、unlock

 std::unique_lock

std::unique_lock<std::mutex> lock(mt);

std::condition_variable 条件变量

std::condition_variable condit;
std::mutex mt;
std::unique_lock<std::mutex> lock(mt);

std::unique_lock<std::mutex> lock(m_mutex);
cond.wait(lock);

std::unique_lock<std::mutex> lock(m_mutex);
cond.notify_all();
睡眠函数相当于sleep
std::this_thread::sleep_for(std::chrono::seconds(1));
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值