Windows下多线程编程(二)

线程的分类

1.     有消息循环线程

  • MFC中有用户界面线程,从CWinThread派生出一个新的类作为UI线程类CUIThread,然后调用AfxBeginthread(RUNTIME_CLASS(CUIThread));启动线程。UI线程可以直接创建模态对话框,而不用担心消息循环的问题,因为UI线程默认自带消息循环。
  • MFC非用户界面线程,不能创建模态对话框,但是可以创建非模态对话框或普通窗口,但是必须自己写消息循环。

  

MSG msg;

while(GetMessage(&msg, NULL, 0, 0))

   {

     TranslateMessage(&msg);

      DispatchMessage(&msg);

  }

2.     无消息循环线程

  • MFC中的工作者线程
  • 其他没有加消息循环的普通线程。

线程间的通信

1.   共享内存变量

l  因为线程是共享进程内存的,所以通过全局/静态变量来进行通信效率最最高的。参数需要考虑是否加volitile。

l  通过传递的参数,如引用和指针。参数需要考虑是否加volitile。

2.   消息通知

  • 如果是子线程向主线程通信,因为主线程有消息循环,所以子线程可以通过发送消息来向主线程通信。通过消息通信能够避免使用全局变量带来的耦合性。

SendMessage必须等待消息函数处理完成才返回,PostMessage则直接将消息放入消息队列立即返回。所以SendMessage的消息参数可以是临时变量,而PostMessage的消息参数必须保证足够的生存周期。

  • 如果子线程有自定义的消息循环,也可以通过PostThreadMessage来指定线程通信。
 while(true)

         {

            if(GetMessage(&msg,0,0,0)) //get msgfrom message queue

            {

                switch(msg.message)

                    {

                    case MY_MSG:

// Todo:

                break;

                    }

             }

         };

3.   其他方式

  • 所有跨进程的通信方式,当然可以用于跨线程了。

线程之间的状态

1.   异步

即多个线程彼此独立,不受外部线程的影响。线程本身就是实现异步的一种方式。

2.   同步

即多个线程彼此依赖,线程A的计算结果是线程B的计算的前提,也就是说在开始线程B的计算之前必须等待线程A的计算完。

3.   互斥

即多个线程在操作同一个资源时,一个线程必须等另一个线程结束了才能继续操作。互斥与同步不同之处是,互斥没有先后关系。同一个资源,可以指全局变量,也可以指一个文件对象或是其他的内核对象。因为内核对象是跨进程的,所以更是跨线程的。

等待函数

1.    概念

WaitForSingleObject函数是等待内核对象从无信号状态到有信号状态或是超时即返回。也即无信号状态时等待,有信号或超时立即返回。

WaitForMulitpleObjects函数是等待多个内核对象从无信号状态到有信号状态或是超时即返回(可以指明是所有对象或是任一对象)。

Windows拥有几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出/错误流、事件、等待定时器、信号量、互斥对象。

2.    等待函数与内核对象之间的关系


对象


无信号状态


有信号状态


成功等待副作用


进程


进程活动时


进程终止时



线程


线程活动时


线程终止时



文件


I/O请求正在处理时


I/O请求结束时



控制台输入


不存在任何输入


存在输入时



文件修改通知


没有任何文件修改通知


文件系统发现修改时


重置通知


自动重置事件


ResetEvent, PulseEvent或等待成功


当调用SetEvent或PulseEvnet时


重置事件


人工重置事件


ResetEvent,或PulseEvent


当调用SetEvent或PulseEvnet时



自动重置定时器


CancelWaitableTimer或等待成功


当时间到时(SetWaitableTimer)


重置定时器


人工重置定时器


CancelWaitableTimer


当时间到时(SetWaitableTimer)



信号量


等待成功


当资源数量>0时(ReleaseSemaphore)


数量减1


互斥量


等待成功


当未被线程拥有时(ReleaseMutex)


获取线程所有权

l 线程和进程创建及运行时都是无信号状态,当结束运行时变为有信号状态。

l 自动重置的事件(FALSE)对象,当等待成功的时候,会被修改为无信号状态。

l 信号量对象,当调用ReleaseSemaphore(数量加1),处于有信号状态,WaitForSingleObject会被触发并且立即将信号数量减1.

用户模式与内核模式的优缺点

1.   用户模式

优点:线程同步机制速度快

缺点:容易陷入死锁状态多个进程之间的线程同步会出现问题。(比如竞争资源、死锁)

2.   内核模式

优点:支持多个进程之间的线程同步,防止死锁

缺点:线程同步机制速度慢,线程必须从用户模式转为内核模式。这个转换需要很大的代价:往返一次需要占用x 8 6平台上的大约1 0 0 0个C P U周期。

线程间的状态处理

1.   线程的异步

因为线程本身就是异步的。

2.   线程的同步

线程的同步主要是通过事件(Event)内核对象、信号量(Semaphore)内核对象和互斥量(Mutex)内核对象。因为都是内核对象,所以不仅可以跨线程操作,还可以跨进程同步。

1.      线程的同步

线程的同步主要是通过事件(Event)内核对象、信号量(Semaphore)内核对象和互斥量(Mutex)内核对象。因为都是内核对象,所以不仅可以跨线程操作,还可以跨进程同步。

事件(Event)内核对象

事件分两种类型:人工重置事件和自动重置事件,前者在触发WaitForSingleObject之后需要手动调用ResetEvent将事件设置为无信号;而后者在触发WaitForSingleObject之后自动将事件设置为无信号状态。

常用函数:

CreateEvent,创建事件对象。

OpenEvent,打开已经创建的事件对象,可以跨进程打开。

SetEvent,将事件对象设置为有信号状态。

ResetEvent,将事件对象设置为无信号状态。

PulseEvent,将事件对象设置为有信号状态,然后又设置为无信号状态,此函数不常用。

HANDELg_hEvent;

int Main()

{

g_hEvent =CreateEvent(NULL, TRUE, FALSE, NULL);

_beginthreadex(NULL,0, ThreadFun1, 0);

_beginthreadex(NULL,0, ThreadFun2, 0);

SetEvnet(g_hEvent);//

}

DWORD WINAPIThreadFun1(PVOID pParam)

{

WaitForSingleObject(g_hEvent);

// Todo...

SetEvent(g_hEvnet);

return 0;

}

DWORD WINAPIThreadFun2(PVOID pParam)

{

WaitForSingleObject(g_hEvent);

// Todo...

SetEvent(g_hEvnet);

return 0;

}

注意:如果上面创建的是人工重置事件,则两个线程函数都将执行。如果是自动重置事件,则只能执行一个线程,且不能保证哪一个线程先执行。如果要保证一个线程先执行,可以添加事件对象用来确保指定线程已经执行,不能通过代码的先后顺序确保线程已经执行。

2.      信号量(Semaphore)内核对象

信号量的使用规则:

当前信号量资源数大于0,则标记为有信号状态。

当前信号量资源数为0,则标记为无信号状态。

信号量资源数不能为负,且最大不能超过指定数量。

常用函数:

CreateSemaphore,创建信号量对象。

OpenSemaphore,打开指定信号量对象,可以跨进程。

ReleaseSemaphoer,资源计算加1。

HANDELg_hSema[2];

int Main()

{

g_hSema[0] =CreateSemaphore(NULL, 1, 1, NULL);

g_hSema[1] =CreateSemaphore(NULL, 0, 1, NULL);

_beginthreadex(NULL,0, ThreadFun1, 0);

_beginthreadex(NULL,0, ThreadFun2, 0);

}

DWORD WINAPIThreadFun1(PVOID pParam)

{

WaitForSingleObject(g_hSema[0]);

// Todo...

ReleaseSemaphoer(g_hSema[1]);

return 0;

}

DWORD WINAPIThreadFun2(PVOID pParam)

{

WaitForSingleObject(g_hSema[1]);

// Todo...

ReleaseSemaphoer(g_hSema[0]);

return 0;

}

这样就能够保证ThreadFun1执行完了,再执行ThreadFun2,然后再执行ThreadFun1,并且保证每个线程函数只能被调用一次.

3.      互斥量(Mutex)内核对象

互斥量内核对象确保线程拥有单个资源的互斥访问权。在行为特性上,互斥量与临界区的一样。只不过,互斥量是内核对象,使用时需要从用户模式切换到内核模式,比较耗时。但正因为是内核对象,所以互斥量能够跨进程,并且能够设置超时时间,这是它比临界区灵活的地方。

常用函数:

CreateMutex,创建互斥量对象。

OpenMutex,打开指定互斥量对象,可以跨进程。

ReleaseMutex,释放互斥量,对象被标记为有信号状态,触发WaitForSingleObject。

互斥量和临界区一样,拥有一个线程拥有权的概念,即当前互斥量和当前临界区的释放只能由当前线程释放,其他线程释放无效。因为互斥量是内核对象,如果线程已经终止,但是其所属的互斥量依然没有释放,内核管理器会自动释放。临界区没有这个功能,因为临界区不是内核对象,所以临界区如果没有正确释放会导致死锁。

HANDLECreateMutex(  LPSECURITY_ATTRIBUTESlpMutexAttributes,

BOOL bInitialOwner,  LPCTSTR lpName);

bInitialOwner标记是否由创建线程拥有线程所有权,TRUE表示创建者拥有,FALSE表示创建者不拥有,则是第一个调用WaitForSingleObject的线程将获得线程所有权。

HANDELg_hMutex;

int Main()

{

g_hMutex =CreateMutex(NULL,FALSE);

_beginthreadex(NULL,0, ThreadFun1, 0);

_beginthreadex(NULL,0, ThreadFun2, 0);

}

DWORD WINAPIThreadFun1(PVOID pParam)

{

WaitForSingleObject(g_hMutex);

// Todo...

ReleaseMutex(g_hMutex);

return 0;

}

DWORD WINAPIThreadFun2(PVOID pParam)

{

WaitForSingleObject(g_hMutex);

// Todo...

ReleaseMutex(g_hMutex);

return 0;

}

两个函数谁先调用,谁即获取线程所有权。如果想指定线程先运行,需要判断指定线程已经执行之后再创建新线程,不能依靠线程的代码创建先后顺序。

3.   线程的互斥

像互斥量对象同样可以达到互斥的效果,只是互斥量功能更丰富,并且如果是简单的资源互斥,使用临界区的效率更优。

临界区(Critical Section)是一段供线程独占式访问的代码,也就是说若有一线程正在访问该代码段,其它线程想要访问,只能等待当前线程离开该代码段方可进入,这样保证了线程安全。他工作于用户级(相对于内核级),在Window系统中CRITICAL_SECTION实现临界区相关机制。

常用函数:

voidInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection)  // 初始化临界区

voidEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)       // 进入临界区

voidLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection)       // 离开临界区

voidDeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)      // 释放临界区资源

因为临界区拥有线程所有权这个概念,即进入临界区的线程才有权释放临界区。因为必须当前线程进入和释放,更多的时候,临界区是在一个函数里使用,为了确保不会由于中间退出函数导致没有释放,我们可以用下列方式来确保释放。

class Mutex {

public:

  Mutex()                      {InitializeCriticalSection(section); }

  ~Mutex()                     { DeleteCriticalSection(section);}

  void Enter()                {EnterCriticalSection(section); }

  void Leave()                {LeaveCriticalSection(section); }

  struct Lock;

protected:

  Mutex(const Mutex&);

  Mutex& operator=(const Mutex&);

  CRITICAL_SECTION section;

};

structMutex::Lock {

  Mutex& s;

  Lock(Mutex& s) : s(s) { s.Enter(); }

  ~Lock()               { s.Leave(); }

};

DWORD WINAPIThreadFun(PVOID pParam)

{

Mutex::Locklock(mutex);

// Todo...

return 0;

}

注意

1.      注意所有内核对象在结束时都需要调用closeHandle()。

2.      跨线程调用MFC对象函数都是不安全的。因为MFC对象的一些函数都与TLS有关联,  所以有些调用会出错。如UpdateData(),最好通过句柄发消息来完成相应的功能。

时间: 2024-12-26 05:18:06

Windows下多线程编程(二)的相关文章

Windows下多线程编程(一)

前言 熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. 进程与线程 1. 进程的概念 进程就是正在运行的程序.主要包括两部分: • 一个是操作系统用来管理进程的内核对象.内核对象也是系统用来存放关于进程的统计信息的地方. • 另一个是地址空间,它包含所有可执行模块或 D L L模块的代码和数据.它还包含动态内 2. 线程的概念 线程就是描述进程的一条执

Windows下多线程编程

熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. 进程与线程 1. 进程的概念 进程就是正在运行的程序.主要包括两部分: ? 一个是操作系统用来管理 ... bbs.chinaacc.com/forum-2-3/topic-5662298.html bbs.chinaacc.com/forum-2-3/topic-5662294.html bbs.ch

linux下多线程编程

最近研究mysql源码,各种锁,各种互斥,好在我去年认真学了<unix环境高级编程>, 虽然已经忘得差不多了,但是学过始终是学过,拿起来也快.写这篇文章的目的就是总结linux 下多线程编程,作为日后的参考资料. 本文将介绍linux系统下多线程编程中,线程同步的各种方法.包括: 互斥量(mutex) 读写锁 条件变量 信号量 文件互斥 在介绍不同的线程同步的方法之前,先简单的介绍一下进程和线程的概念, 它们的优缺点,线程相关的API,读者——写者问题和哲学家就餐问题. 基础知识 1. 进程和

【转】Windows的多线程编程,C/C++

在Windows的多线程编程中,创建线程的函数主要有CreateThread和_beginthread(及_beginthreadex). CreateThread 和 ExitThread    使用API函数CreateThread创建线程时,其中的线程函数原型:  DWORD WINAPI ThreadProc(LPVOID lpParameter);在线程函数返回后,其返回值用作调用ExitThread函数的参数(由系统隐式调用).可以使用GetExitCodeThread函数获得该线程

初探WINDOWS下IME编程

初探WINDOWS下IME编程作者:广东南海市昭信科技有限公司-李建国 大家知道,DELPHI许多控件有IME属性.这么好用的东西VC可没自带,怎么办呢?其实,可通过注册表,用API实现.下面说一下本人对IME的研究结果,并提供示例工程供大家参考: 下载示例工程 10.6K 本文示例程序运行结果如上图1.将用到的API RegOpenKey:打开注册表一键RegQueryValue:查询一键值RegQueryValueEx:同上RegCloseKey:关闭打开的键 LoadKeyboardLay

多线程编程[二]

继 多线程编程[一]:http://www.cnblogs.com/wangfajun/p/6547648.html,我们开始第二篇文章啦... 上一篇中结尾,我们了解到,同步函数用的锁是 this ,那么我们接下来,在同步函数上加下个静态标示符static试试: public class Test { public static void main(String[] args) { try { Ticket one = new Ticket(); new Thread(one).start()

Windows下Hadoop编程环境配置指南

刘勇    Email: [email protected] 本博客记录作者在工作与研究中所经历的点滴,一方面给自己的工作与生活留下印记,另一方面若是能对大家有所帮助,则幸甚至哉矣! 简介 鉴于最近在研究Hadoop编程时,为考虑编程的方便,在Windows本地编译源程序,然后直接访问Hadoop集群,这样给广大编程人员提供了极大的便利.在这个过程中积累了一些实际经验,并针对在该过程中(初级阶段)可能会遇到的问题,提供一些解决方案,希望对大家有所帮助. 环境介绍 Hadoop 集群:hadoop

DELPHI下多线程编程的几个思维误区(QDAC)

有几个网友私下问我一些有关线程的事情.过节写个东西上来大家交流. 思维误区1,自己新建的THREAD是线程,自己的主程序不是线程. 很多人在多线程编程没有把主线程也当作线程.其实主线程也是线程.看起来是废话,这个话确实很重要,这个就意味着,在DELPHI中,不光你开的线程,还有你的主线程所有的内存分配也是串的,进锁排队的.主线程和线程的区别 A.一般来说主线程的优先级高了点.(当然你也可以自己设置) B.主线程在WIN下是处理APPLICATION的消息. 其他基本与你自建线程无区别. 所以这一

Windows下GUI编程——窗口

windows下创建一个基于GUI的窗口程序很简单,使用MFC或者Win32 API都可以实现.本文简单整理下windows API创建GUI应用程序的基本编码框架. 比较常见的窗口包括:桌面窗口.应用程序窗口.对话框及控件.从编程的角度而言,创建GUI应用程序需要提供入口函数WinMain的实现,其定义格式如下: int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS