前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Windows线程漫谈界面线程和工作者线程

Windows线程漫谈界面线程和工作者线程

作者头像
全栈程序员站长
发布2022-07-15 16:13:00
6350
发布2022-07-15 16:13:00
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是全栈君

每个系统都有线程,而线程的最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应力。

线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,这个词应该进到你的脑子里,根深蒂固的!

如果在界面线程的某个地方停住,这说明它处理不了窗口消息了,所以有时候我们就会看到整个界面无响应了。这种问题后面会提供一个叫 WaitForObjectEx 的函数来解决,我们后面再谈。

线程首先就是它的创建,创建是用下面这个函数:CreateThread; 具体的参数我不说了,自己查MSDN。其中的 Thread1 是线程函数。线程函数是一个全局函数,如下:

DWORD WINAPI Thread1(LPVOID lpParam) { while(1) { OutputDebugString(“11111”);

Sleep(10); } return 0; }

// 下面这一句是创建线程 CreateThread(NULL, 0, Thread1, 0, 0, NULL);

当然我们不能让一个线程自生自灭,那样有可能在你退出程序的时候出现一些莫名其妙的问题,或者丢失一些数据,或者给你弹一个崩溃的对话框等等。。。

所以我们就要对这个线程进行管理,首先就是让它退出。

我们给它的while加上一个 BOOL 变量 g_bExitThread的判断,这样的话,线程函数就变成下面这样:

DWORD WINAPI Thread1(LPVOID lpParam) { while(!g_bExitThread) { OutputDebugString(“11111”);

Sleep(10); } return 0; }

然后在需要它退出的时候把g_bExitThread设为TRUE,表示,喂,兄弟,你该退出了。

当然我们还要知道它是否成功退出了,因为线程句柄是一个内核对象,所以我们就要用到Windows的WaitForSingleObject来等待了。创建的时候和等待它退出的代码就要改变了,多了一个 HANDLE g_hTrd的变量:

// 创建 g_bExitThread = FALSE; g_hTrd = CreateThread(NULL, 0, Thread1, 0, 0, NULL);

// 等待线程结束 g_bExitThread = TRUE;

if(g_hTrd != NULL) { DWORD dwRet = WaitForSingleObject(g_hTrd, 5000); if(dwRet == WAIT_OBJECT_0) { AfxMessageBox(“Thread exit success!”); } else { DWORD dwRet = 0; GetExitCodeThread(g_hTrd, &dwRet); TerminateThread(g_hTrd, dwRet); AfxMessageBox(“Thread exit, but not all ok!”); } CloseHandle(g_hTrd); g_hTrd = NULL; }

上面说了在界面线程里等待别的线程结束,也就是使用 WaitForSingleObject 的时候会阻塞整个窗口消息的处理,所以我们如果在界面线程里要等待别的内核对象时,我们要采用这种“等一下,处理一下界面消息”的方法。我已经写好了一个 WaitForObjectEx 的函数,如下:

// 此函数只能用于界面线程 static DWORD WaitForObjectEx( HANDLE hHandle, DWORD dwMilliseconds ) { BOOL bRet; MSG msg; INT iWaitRet; int nTimeOut = 0; while( (bRet = ::GetMessage( &msg, NULL, 0, 0 )) != 0) { if(nTimeOut++ * 20 >= dwMilliseconds) break;

iWaitRet = WaitForSingleObject(hHandle, 20); if(iWaitRet != WAIT_TIMEOUT) { break; } if (bRet == -1) { break; } else { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } }

return iWaitRet; }

很多时候,我们不想把线程作为一个全局函数来使用,所以这个时候我们把线程作为一个类的静态成员对象来写。当然也不能少了刚才的两个变量:退出标志和线程句柄。(设这个类是CTestThreadDlg)

// H 文件 BOOL m_bExitThread; HANDLE m_hTrd; static DWORD WINAPI Thread1(LPVOID lpParam);

// CPP文件,创建的时候把 this 指针传进去,因为类静态成员函数不能访问类的非静态成员,没有this指针 //(C++的知识点) m_bExitThread = FALSE; m_hTrd = CreateThread(NULL, 0, Thread1, this, 0, NULL);

线程函数变成了:

DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam) { CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam; while(!pDlg->m_bExitThread) { OutputDebugString(“11111”); Sleep(10); } return 0; }

当有几个线程一起跑的时候,我们就要注意线程的同步问题了,线程的同步一般来说,是在多个线程共用了资源的时候。比如两个线程都用到了同一个VECTOR,都对VECTOR进行插入操作,不幸的是,VECTOR不是线程安全的,这个时候程序就会崩溃,所以我们就要对VECTOR这个资源做同步,同步的意思是“我访问的时候,你等待”。程序大致如下:

DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam) { CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam; while(!pDlg->m_bExitThread) { OutputDebugString(“11111”); pDlg->m_csForVec.Lock(); pDlg->m_vecTest.push_back(“111”); pDlg->m_csForVec.Unlock(); Sleep(10); } return 0; }

DWORD WINAPI CTestThreadDlg::Thread2(LPVOID lpParam) { CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam; while(!pDlg->m_bExitThread2) { OutputDebugString(“222”);

pDlg->m_csForVec.Lock(); pDlg->m_vecTest.push_back(“222”); pDlg->m_csForVec.Unlock();

Sleep(10); } return 0; }

m_csForVec 是一个CCriticalSection变量,这个同步对象和其他的同步变量(事件、信号量、互斥区等)有一些不一样,例如只能在同一个进程的线程间访问、在操作系统的用户态访问,其他的必须进入核心态。所以这样导致了这种关键区的核心对象的速度要比其他的快100倍左右。。。

上面已经说了线程的创建、管理(退出线程、等待线程)、同步等,那我们发现了什么共性呢?作为一个程序员,我们要很敏感的发现这些代码上的共性,这是我们设计代码的主要前提。

首先我们发现上面的线程都有两个变量: BOOL m_bExitThread; // 让线程退出的标志 HANDLE m_hTrd; // 线程句柄

另外我们WaitForSingleObject 的时候不能无限等待,所以要多一个 DWORD m_dwWaitTimeOut;

由于我想把线程启动和结束封装起来,所以我设计了这几个接口:

BOOL Start(LPVOID lpParam); // 启动线程,线程所需要的参数从这里传进 BOOL End(); // 结束线程 virtual void Run(); // 重写Run函数 hovertree.com

所以整个的线程封装成以下的类:

// MyThread.h

#ifndef MY_THREAD_H #define MY_THREAD_H class CMyThread { public: CMyThread(); virtual ~CMyThread();

BOOL Start(LPVOID lpParam); BOOL End(); virtual void Run();

protected: static DWORD WINAPI Thread(LPVOID lpParam); void RunOnceEnd();

DWORD m_dwWaitTimeOut; BOOL m_bExitThread; HANDLE m_hTrd; LPVOID m_lpParam; };

#endif

// MyThread.Cpp

#include “stdafx.h” #include “MyThread.h” ///////////////////////////////////////////////////////////////////////////// // CMyThread CMyThread::CMyThread() { m_bExitThread = FALSE; m_hTrd = NULL; m_dwWaitTimeOut = 5000; }

CMyThread::~CMyThread() {

}

BOOL CMyThread::Start(LPVOID lpParam) { m_lpParam = lpParam; m_bExitThread = FALSE; m_hTrd = CreateThread(NULL, 0, Thread, this, 0, NULL);

return TRUE; }

BOOL CMyThread::End() { m_bExitThread = TRUE;

if(m_hTrd != NULL) { DWORD dwRet = WaitForSingleObject(m_hTrd, m_dwWaitTimeOut); if(dwRet == WAIT_OBJECT_0) { AfxMessageBox(“Thread exit success!”); } else { DWORD dwRet = 0; GetExitCodeThread(m_hTrd, &dwRet); TerminateThread(m_hTrd, dwRet); AfxMessageBox(“Thread fucking exit!”); }

CloseHandle(m_hTrd); m_hTrd = NULL; } return TRUE; }

DWORD WINAPI CMyThread::Thread(LPVOID lpParam) { CMyThread *pTrd = (CMyThread *)lpParam; while(!pTrd->m_bExitThread) { pTrd->Run(); }

return 0; }

void CMyThread::RunOnceEnd() { m_bExitThread = TRUE; CloseHandle(m_hTrd); m_hTrd = NULL; }

void CMyThread::Run() { }

我们需要写我们自己的线程的时候就重载一下这个Run函数

// 派生出一个类 何问起 class CMyThread1 : public CMyThread { public: virtual void Run(); };

// 改写Run函数 void CMyThread1::Run() { CTestThreadDlg *pDlg = (CTestThreadDlg *)m_lpParam;

OutputDebugString(“222”); pDlg->m_csForVec.Lock(); pDlg->m_vecTest.push_back(“222”); pDlg->m_csForVec.Unlock(); Sleep(10);

// 如果此线程只想运行一次,加上下面这句 RunOnceEnd(); }

然后我们之前的两个线程的使用就变成了下面的形式:

CMyThread1 g_t1, g_t2, g_t3; void CTestThreadDlg::OnButton3() { g_t1.Start(this); g_t2.Start(this); g_t3.Start(this); }

void CTestThreadDlg::OnButton4() { g_t1.End(); g_t2.End(); g_t3.End(); }

只需要以下几步: 1、派生自己的线程类 2、重载Run函数 3、调用Start启动线程 4、调用End结束线程

当然这种封装方式是我自己喜欢的,封装的目的是方便使用,隐藏细节,诸位看官也可以根据自己的喜好,封装线程的使用方法,如果能在此公开一下你的成果,让我和大家都学习一下你的设计手法,那就真是very good and 3q了!

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/120439.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年12月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档