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/

时间: 2024-10-23 23:59:01

Windows线程漫谈界面线程和工作者线程的相关文章

浅谈服务器单I/O线程+工作者线程池模型架构及实现要点

转自 http://www.cnblogs.com/ccdev/p/3542669.html 单I/O线程+多工作者线程的模型,这也是最常用的一种服务器并发模型.我所在的项目中的server代码中,这种模型随处可见.它还有个名字,叫“半同步/半异步“模型,同时,这种模型也是生产者/消费者(尤其是多消费者)模型的一种表现. 这种架构主要是基于I/O多路复用的思想(主要是epoll,select/poll已过时),通过单线程I/O多路复用,可以达到高效并发,同时避免了多线程I/O来回切换的各种开销,

窗体和线程漫谈之工作线程怎样将数据的处理结果显示到窗体

前言 原本这篇博客的标题叫<窗体和线程漫谈>,但想来想去确实不太合适.由于我确实没有写关于窗体和线程的不论什么理论知识,而仅仅是探讨了工作线程怎样将数据的处理结果显示到窗体这个问题,因此又一次改动标题. 另外,关于窗体和线程的相关理论知识.感觉一两句话确实说不清楚,并且<Windows 核心编程>这本书上介绍的也挺好的.有机会再写吧.特别是感觉如今好多人都直接在学 MFC,用 MFC.甚至连窗体过程,消息循环都不太明确,假设能有这样一篇博客也是非常有价值的. 为什么要讨论这个问题

二、UI线程和界面卡死

上回说到,在Windows窗体程序中,响应Windows消息的线程就被称做Windows窗体程序的UI线程.UI线程还有一个重要的功能是创建和管理窗体和窗体中的各种控件,负责他们的实时刷新,如果UI线程在处理某个消息的时候耗时特别长,那么后续的消息就无法及时响应,看上去的感觉就是“界面卡死”了.此外,为了避免出现线程安全类的问题,UI控件是不能多线程访问的,一个backgroundworker线程直接去刷新控件,这是绝对不允许的,但这种需求又是客观存在的(比方说从数据库中获取数据后刷新到控件中)

扩展BindingList,防止增加、删除项时自动更新界面而不出现“跨线程操作界面控件 corss thread operation”异常

在做界面程序时,常常需要一些数据类,界面元素通过绑定等方式显示出数据,然而由于UI线程不是线程安全的,一般都需要通过Invoke等方式来调用界面控件.但对于数据绑定bindingList而言,没法响应listchang事件,导致后端的grid等控件不能更新数据.废了好大的劲终于找到一个UIBindingList,实现线程数据的同步! using System; using System.ComponentModel; using System.Threading; using System.Wi

MFC工作者线程

//************工作者线程**************1.在头文件中添加UINT ThreadFunc(LPVOID lpParam); 注意应在类的外部 2.添加protected型变量:CWinThread* pThread 3.添加线程处理函数:UINT ThreadFunc(LPVOID lpParam){ while (true) { ... Sleep(1000); } return 0;} 4.开启线程AfxBeginThread(ThreadFunc,lpParam)

使用winform控件注意线程绘制界面冲突

在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写: void ButtonOnClick(object sender,EventArgs e) { button.Text="关闭"; } 这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”.注意这里是“可能”,并不一定会触发该种异常

windows 一个进程可以允许最大的线程数

默认情况下,一个线程的栈要预留1M的内存空间 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小. 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程. 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程. 即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制. 比方说你的机器装了64GB物理内存,但每个进程的内存空间还是4GB,其中用户态可用

Winform中子线程访问界面控件时被阻塞解决方案

public partial class WebData_Import : Form { //声明用于访问主界面的委托类型 public delegate void deleGetOrderdata(string info); //声明访问主界面委托类型的变量 public deleGetOrderdata OptGetOrderData; int CompanyID = 0; public WebData_Import() { InitializeComponent(); cmbCompany

WINDOWS操作系统中可以允许最大的线程数(线程栈预留1M空间)(56篇Windows博客值得一看)

WINDOWS操作系统中可以允许最大的线程数 默认情况下,一个线程的栈要预留1M的内存空间 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小. 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程. 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程. 即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制. 比方说你的机器装了64GB物理内存,