Windows线程漫谈界面线程和工作者线程
每个系统都有线程,而线程的最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应力。
线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,这个词应该进到你的脑子里,根深蒂固的!
如果在界面线程的某个地方停住,这说明它处理不了窗口消息了,所以有时候我们就会看到整个界面无响应了。这种问题后面会提供一个叫 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了!
http://www.cnblogs.com/roucheng/
Windows线程漫谈界面线程和工作者线程的更多相关文章
- UI线程和工作者线程
本文转载于:http://blog.csdn.net/libaineu2004/article/details/40398405 1.线程分为UI线程和工作者线程,UI线程有窗口,窗口自建了消息队列, ...
- C#线程学习笔记二:线程池中的工作者线程
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,记录一下学习过程以备后续查用. 一.线程池基础 首先,创 ...
- WebWorker:工作者线程初探
WebWorker:工作者线程初探 参考资料: 1.Web Worker 使用教程 - 阮一峰:http://www.ruanyifeng.com/blog/2018/07/web-worker.ht ...
- C#线程学习笔记三:线程池中的I/O线程
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/20/MultiThreads.html,记录一下学习过程以备后续查用. 一.I/O线 ...
- 扩展BindingList,防止增加、删除项时自动更新界面而不出现“跨线程操作界面控件 corss thread operation”异常
在做界面程序时,常常需要一些数据类,界面元素通过绑定等方式显示出数据,然而由于UI线程不是线程安全的,一般都需要通过Invoke等方式来调用界面控件.但对于数据绑定bindingList而言,没法响应 ...
- delphi 线程教学第二节:在线程时空中操作界面(UI)
第二节:在线程时空中操作界面(UI) 1.为什么要用 TThread ? TThread 基于操作系统的线程函数封装,隐藏了诸多繁琐的细节. 适合于大部分情况多线程任务的实现.这个理由足够了吧 ...
- java查看当前项目所有线程列表界面
java查看当前项目所有线程列表界面 1.TestThread(测试类) package com.isoftstone.pcis.isc.job.king.panel; public class Te ...
- 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法
[源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...
- 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLock
[源码下载] 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLoc ...
随机推荐
- 继电器是如何成为CPU的(2)
继电器是如何成为CPU的(2) ——<穿越计算机的迷雾>整理和总结 上一篇已经从电池.开关.灯泡和继电器开始,画出了设计CPU所需的基本器件.这些器件将成为设计CPU的砖瓦木料.这一篇就用 ...
- Git学习笔记(4)——添加远程仓库,克隆远程库,以及库的推送
本文记录了远程库的连接和库的克隆和推送. 远程仓库简介 Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上.有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且 ...
- java内功 ---- jvm虚拟机原理总结,侧重于虚拟机类加载执行系统
参考书籍:<深入理解java虚拟机>,三天时间用了八个小时看完,像读一本武侠小说,挺爽. 另外需声明:图片都是从我自己的csdn博客转载,所以虽然有csdn标识,但都是我自己画的图片. j ...
- 程序员DNS知识指南
本次内容比较hardcore,非科班出身可能会有理解障碍,可以考虑不用挣扎,直接放弃.毕竟普通驾驶员是不需要理解内燃机原理和曲轴如何做功的,所谓术业有专攻也,能看完的不是同行就是真爱~ 引 DNS对于 ...
- js菜单默认选中
function defaultSelected() { var curr = $("#leftTree li[onclick*='" + $("#content_ifr ...
- js笔记——call,apply,bind使用笔记
call和apply obj.call(thisObj, arg1, arg2, ...); obj.apply(thisObj, [arg1, arg2, ...]); 两者作用一致,都是把obj( ...
- Atitit. Api 设计 原则 ---归一化
Atitit. Api 设计 原则 ---归一化 1.1. 叫做归一化1 1.2. 归一化的实例:一切对象都可以序列化/toString 通过接口实现1 1.3. 泛文件概念.2 1.4. 游戏行业 ...
- Java面试(1)-- Java赋值表达式
1 class Demo01{ 2 public static void main(String[] args){ 3 //赋值运算符 = 4 5 //例1 6 int a = 1; 7 System ...
- asp.net 有关时间各种(输出)处理
有关一下时间处理方法 /// <summary> /// 将时间转换成几小时.几天之类的格式 /// </summary> /// <param name="m ...
- angular测试-Karma + Jasmine配置
首先讲一下大致的流程: 需要node环境,首先先要安装node,node不会?请自行搜索.版本>0.8 安装node完成之后先要测试下npm是否测试通过,如下图所示 首先看下目录结构 目录为:F ...