多线程编程与事件

时间片的概念大家应该都了解过,windows为每个进程分配时间片,当一个程序的时间片结束时,就会轮到下一个程序执行,当轮流速度比较快的时候,就好像多个进程同时执行一样,这就是windows多任务的方式,本次介绍的多线程其实跟这个很像,只不过线程是在进程内又划分的更小的可执行单位,windows为每个线程分配时间片,当轮到某个进程的某个线程执行的时候,该线程才开始执行,就好像是进程间的多任务一样,线程依赖于进程,进程结束则线程必定结束,线程结束则进程不一定结束,线程就是一个进程中的某个可执行的模块,有单独完成某项功能的作用,正因为如此才会出现多线程编程,我现在理解的多线程编程就是,可以将某个需要长时间处理的子模块单独在一个线程中完成,比如说一个线程处理带有界面的子模块,如果在这个线程中加入一些需要长时间运算的功能代码的话,那么这个界面就会卡到那(就如同我们平时遇见的某个程序的界面未响应,卡到那没有反映一样,这个很烦人吧),如果将需要计算的那个功能放在一个独立的模块,建立一个新的线程执行,将结果传递到那个需要处理界面的线程的模块中,这样就不会出现那种卡顿的现象了(对于现在的计算机一班都是多处理器,这样会有更快的处理速度),下面来介绍一下利用多线程与事件实现一个计数器。首先看一下程序的流程:

资源文件
图标 IDI_ICON2
对话框 IDD_DIALOG1
静态文本 不需要ID,不需要对其操作
编辑文本 IDC_COUNTER
按钮 IDOK         IDC_PAUSE

主程序
计数函数 _COUNTER
对画框消息处理 _ProcDlgMain
模态对话框 DialogBoxParam

首先是资源文件的编写,依然使用ResEdit工具编写,资源文件编写不在赘述,很简单注意风格就行,直接看一下代码:

// Generated by ResEdit 1.6.6

// Copyright (C) 2006-2015

// http://www.resedit.net

#include <windows.h>

#include <commctrl.h>

#include <richedit.h>

#include "resource.h"

//

// Dialog resources

//

LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL

IDD_DIALOG1 DIALOG 0, 0, 186, 95

STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU

CAPTION "多线程与事件"

FONT 8, "Ms Shell Dlg"

{

EDITTEXT        IDC_COUNTER, 55, 20, 124, 16, ES_AUTOHSCROLL, WS_EX_LEFT

LTEXT           "计数值显示:", 0, 2, 24, 49, 9, SS_LEFT, WS_EX_LEFT

PUSHBUTTON      "暂停/恢复", IDC_PAUSE, 117, 65, 48, 14, 0, WS_EX_LEFT

DEFPUSHBUTTON   "计数", IDOK, 21, 66, 48, 14, 0, WS_EX_LEFT

}

//

// Icon resources

//

LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL

IDI_ICON2          ICON           "icon2.ico"

下面来看一下程序实现部分,这次程序编写遇到好多问题,有历史问题还有新的问题,首先看一下历史问题(以前理解的总感觉不是很扎实,不稳固,有点迷茫的感觉)今天彻底梳理一下,这个问题就是关于设置标志的问题,关于设置标志,有什么用呢,其实就是为了方便判断以及操作而设置的一个标志而已,有时标志会根据对话框按钮的状态来设置,比如本次程序的下面的三个标志,只说不练也是徒劳,下面来根据具体的使用来学习一下.

本次程序中使用的标志变量是dwOption    设置的三个标志分别为:

F_PAUSE             equ        0001h                  ;二进制为:0001      (只写16进制的最后一位 为2进制)

F_STOP               equ        0002h                   ;二进制为:0010

F_COUNTING      equ        0004h                   ;二进制为:0100

首先来看一下第一个例子也就是计数模块:

Counter        proc                                                                 

or         dwOption,F_COUNTING                     ;初始状态dwOption为0,dwOption(0) 与F_COUNTING(0100)

                                                                                       按位相或结果为:0100   (其实也就是置位)

and        dwOption,not (F_STOP or F_PAUSE)         ;上句代码之后 dwOption(0100)与 not(F_STOP                                                                                                      or  F_PAUSE  )按位相与。首先来看  
                                                                                                                     F_STOP(0010) 与 F_PAUSE(0001)   
按位相或 结果                                                
                                                      为:0011     再加上not(0011)---》(1100)    然                                                                                                      后dwOption(0100)与(1100)  按位相与,结果                              
                                                                     为0100   也就是F_COUNTING     

                                                                                                     总结:上面这两句代码的意思其实就是为了将                                                                                                
             dwOption设置为F_COUNTING,从第二句可以知                                                                                                       道not (F_STOP or F_PAUSE)  结果为(1100)

                                                                                                       F_STOP  与F_PAUSE   F_COUNTING与                                                                                    
                                    (1100)相与之后,只有F_COUNTING 有效,                                                                                                   其他两个直接无效,而第一句正好将dwOption设置                                                                    
                                为F_COUNTING,也就是代表现在的IDOK按钮的                                                                                                       显示的状态为  计数中

invoke     SetEvent,hEvent

invoke     SetWindowText,hWinCount,addr szStop

invoke     EnableWindow,hWinPause,TRUE

xor        ebx,ebx

.while     ! (dwOption & F_STOP)                    ;dwOption与F_STOP相同则为真  ,!真即是假(false)                                                                                            则跳出循环,如果为其它标志,则为假,!假即是真,

                                                                                       执行循环语句,注意这个不是按位操作(个人理解)       

inc     ebx

invoke  SetDlgItemInt,hWinMain,IDC_COUNTER,ebx,FALSE

invoke  WaitForSingleObject,hEvent,INFINITE

.endw

invoke     SetWindowText,hWinCount,addr szStart

invoke     EnableWindow,hWinPause,FALSE

and        dwOption,not (F_COUNTING or F_PAUSE or F_STOP)             ;最后利用按位操作符将dwOption

                                                                                                                              复位(就是不再是任何一种标志)

ret

_Counter endp

下面来看一下第二个问题,这个问题是个新的问题,那就是事件对象的用法,事件对象可以看做一个设置在Windows内部的标志,它的状态设置和测试工作由windows来完成,windows可以将这些标志的设置和测试工作和线程调度等工作在内部结合起来,这样效率很高。事件可以分为两种状态:置位  和 复位 这两个操作可以通过SetEvent   和   ResetEvent  两个函数分别实现函数(从英文字面意思也可以看出来,谁让这是外国人写的呢。。),这其实和上一个问题中的使用
or   和  and  操作是标志变量 置位和复位的操作一样。如果将事件看成是“标志”的话,可以用WaitForSingleObject函数来检测,这个函数有一点 特殊,那就是当该函数返回时,需要满足两种状态:

1.测试事件对象的状态为置为状态

2.到了dwMilliseconds指定的超时时间 (这个是这个函数的一个参数,详见后面对函数的介绍)

这样,我们可以在程序初始化的时候建立事件对象,当作标志用,然后我们可以在对话框的消息处理中,当接收到IDOK按钮的消息时,将事件对象设置为 置位。然后在计数模块中使用WaitForSingleObject  函数测试事件的状态,当状态为置位时,函数可以正常返回,否则不会返回,函数会等待,不再出现循环测试标志位的情况,而主要依靠的是事件的状态情况。

下面来看一下具体的实现代码:

                .386

.model flat,stdcall

option casemap:none

include         windows.inc

include         user32.inc

includelib      user32.lib

include         kernel32.inc

includelib      kernel32.lib

IDI_ICON2       equ        101

IDD_DIALOG1     equ        102

IDC_COUNTER     equ        40001

IDC_PAUSE       equ        40002

.data?

hInstance       dd         ?

hWinMain        dd         ?

hWinCount       dd         ?

hWinPause       dd         ?

hEvent          dd         ?

dwOption        dd         ?

F_PAUSE         equ        0001h

F_STOP          equ        0002h

F_COUNTING      equ        0004h

.const

szStop          db         ‘停止计数‘,0

szStart         db         ‘计数‘,0

.code

_Counter        proc

or         dwOption,F_COUNTING

and        dwOption,not (F_STOP or F_PAUSE)

invoke     SetEvent,hEvent

invoke     SetWindowText,hWinCount,addr szStop

invoke     EnableWindow,hWinPause,TRUE

xor        ebx,ebx

.while     ! (dwOption & F_STOP)

inc     ebx

invoke  SetDlgItemInt,hWinMain,IDC_COUNTER,ebx,FALSE

invoke  WaitForSingleObject,hEvent,INFINITE

.endw

invoke     SetWindowText,hWinCount,addr szStart

invoke     EnableWindow,hWinPause,FALSE

and        dwOption,not (F_COUNTING or F_PAUSE or F_STOP)

ret

_Counter endp

_ProcDlgMain    proc       uses ebx edi esi hWnd,wMsg,wParam,lParam

LOCAL      @dwThreadID

mov        eax,wMsg

.if        eax == WM_COMMAND

mov      eax,wParam

.if      ax == IDOK

.if   dwOption & F_COUNTING

invoke  SetEvent,hEvent

or      dwOption,F_STOP

.else

invoke  CreateThread,NULL,0,offset _Counter,NULL,NULL,addr @dwThreadID

invoke  CloseHandle,eax

.endif

.elseif  ax == IDC_PAUSE

xor   dwOption,F_PAUSE

.if   dwOption & F_PAUSE

invoke  ResetEvent,hEvent

.else

invoke  SetEvent,hEvent

.endif

.endif

.elseif    eax == WM_CLOSE

invoke CloseHandle,hEvent

invoke EndDialog,hWnd,NULL

.elseif    eax == WM_INITDIALOG

push   hWnd

pop    hWinMain

invoke GetDlgItem,hWnd,IDOK

mov    hWinCount,eax

invoke GetDlgItem,hWnd,IDC_PAUSE

mov    hWinPause,eax

invoke CreateEvent,NULL,TRUE,FALSE,NULL

mov    hEvent,eax

.else

mov    eax,FALSE

ret

.endif

mov        eax,TRUE

ret

_ProcDlgMain    endp

start:

invoke     GetModuleHandle,NULL

mov        hInstance,eax

invoke     DialogBoxParam,eax,IDD_DIALOG1,NULL,offset _ProcDlgMain,NULL

invoke     ExitProcess,NULL

end        start

下面来介绍一下几个API函数:

CreateEvent(

功能:

 它用来创建或打开一个命名的或无名的事件对象。

原型:

HANDLECreateEvent(

LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性

BOOLbManualReset,// 复位方式

BOOLbInitialState,// 初始状态

LPCTSTRlpName // 对象名称

);

 参数:

lpEventAttributes

一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。

Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。

bManualReset

指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。

bInitialState

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

lpName

指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。

如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。

如果lpName为NULL,将创建一个无名的事件对象。

如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。

返回值:

如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。          

WaitForSingleObject()

功能:

 用来测试事件,线程,和进程等对象的状态

原型:

DWORD WINAPI WaitForSingleObject(

__in HANDLE hHandle,

__in DWORD dwMilliseconds

);

参数:

hHandle[in]对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。

当等待仍在挂起状态时,句柄被关闭,那么函数行为是未定义的。该句柄必须具有 SYNCHRONIZE 访问权限。

dwMilliseconds[in]定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。

返回值:

WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。

WAIT_OBJECT_0 0x00000000 :核心对象已被激活

WAIT_TIMEOUT 0x00000102:等待超时

WAIT_FAILED 0xFFFFFFFF :出现错误         

   

SetEvent()

功能:

设置事件的状态为有标记,释放任意等待线程

     原型:

     BOOL SetEvent(HANDLE hEvent);

参数;

hEvent表示事件句柄

返回值:

如果操作成功,则返回非零值,否则为0。

ResetEvent()

功能:

这个函数把指定的事件对象设置为无信号状态(就是复位)

原型:

BOOL ResetEvent(

HANDLE hEvent

);

参数:

hEvent

[in] 指向事件对象的句柄.由 CreateEvent or OpenEvent 函数返回。 这个句柄需要拥有EVENT_MODIFY_STATE 访问权限.

返回值:

函数成功,返回非0值,否则返回0值,可以调用GetLastError得到错误的详细信息

CreateDialogParam()

功能:

函数根据对话框模板资源创建一个无模式(非模态)的对话框。在显示对话框之前,函数把一个应用程序定义的值作为WM_INITDIALOG消息IParam参数传到对话框过程应用程序可用此值来初始化对话框控制

原型:

HWND CreateDialogParam(HINSTANCE
hlnstancem,LPCTSTR IpTemplateName,HWND hWndParent,DLGPROCIpDialogFunc, LPARAM dwlniParam);

参数:

hlnstance:标识一个模块的事例,该模块的可执行文件含有对话框模板。

IpTemplateName:标识对话框模板。此参数可以指向一个以NULL结尾的字符串的指针,该字符串指定对话框模板名,或是指定对话框模板的资源标识符的一个整型值。如果此参数指定了一个资源标识符,则它的高位字一定为零且低位字一定含有标识符。一定用MAKEINTRESOURCE宏指令创建此值。

HwndParent:指定拥有对话框的窗口。

IpDialogFunc:指向对话框过程的指针。有关对话框过程的更详细的信息,请参见DialogProc。

dwlnitParam:指定传递到WM_INITDIALOG消息的IParam参数中的对话框过程的值。

返回值:

如果函数调用成功则返回值为指向对话框的窗口句柄。如果函数调用失败则返回值为NULL。

时间: 2024-07-29 14:33:40

多线程编程与事件的相关文章

第73课 Qt中的多线程编程

1. QThread类 (1)QThread是一个跨平台的多线程解决方案 (2)QThread以简洁易用的方式实现多线程编程 2. QThread中的关键成员函数 (1)virtual void run() :线程函数,用于定义线程功能(执行流). (2)void start():启动函数,将线程入口地址设置为run函数.启动线程,新线程开始执行run函数. (3)int exec():进入事件循环,直至调用exit().返回线程退出事件循环的返回码. (4)void terminate():强

多线程编程基础知识

多线程编程基础知识 http://www.cnblogs.com/cy163/archive/2006/11/02/547428.html 当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力.用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义.现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的.因此掌握

多线程编程1-NSThread

前言 每个iOS应用程序都有个专门用来更新显示UI界面.处理用户触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验.一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法. iOS中有3种常见的多线程编程方法: 1.NSThread 这种方法需要管理线程的生命周期.同步.加锁问题,会导致一定的性能开销 2.NSOperation和NSOperationQueue 是基于OC

Android多线程编程(一)——多线程基础

什么是进程 一个进程是一个独立(self contained)的运行环境,它可以看作一个程序或者一个应用. 什么是线程 而线程是进程中执行的一个任务,Java运行环境是一个包含了不同累和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. Android线程 Android的线程,实际上和Java的多线程编程并没有什么本质上的不同.当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如

Swing多线程编程(转)

关键字: Swing,多线程,GUI,SwingWorker 摘要: 本文论述了怎样开发多线程的Swing程序,从而提高Swing程序的响应速度和性能. 近期,我将推出一系列研究Swing程序的文章,这也算是为了向Swing这个优秀的GUI库的设计者致敬吧! Swing这种优秀的GUI库一直不能占领桌面市场,实在令人费解,今天,我就用我的努力,为java在桌面市场的成功尽我微薄之力吧! Swing的单线程开发机制 多线程开发,显然要比单线程开发有趣.高效.美妙得多.特别是在Java这种天生支持多

多线程编程学习总结(转载)

线程的概念和原理 为什么使用多线程? 为了更高效的完成任务和利用CPU资源,现在的操作系统设计为多任务操作系统,而多进程和多线程是实现多任务的方式. 什么是进程和线程? 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程.进程是OS分配资源的最小单位. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.线程总是属于某个进程,进程中的多个线程共享进程的内存.进程是OS调度的最小单位. 工作原理? 多线程是这样一种机制,它允许在程序中并发执行多个

多线程编程1

参考资料: http://blog.csdn.net/JXH_123/article/details/23450031                             秒杀多线程系列 http://www.baidu.com/index.php?tn=utf8kb_oem_dg&addresssearch=1#wd=C%2B%2B%E5%BE%AA%E7%8E%AF%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97&ie=utf-8&tn=utf8kb

C#多线程编程

C#多线程编程 一.使用线程的理由 1.可以使用线程将代码同其他代码隔离,提高应用程序的可靠性. 2.可以使用线程来简化编码. 3.可以使用线程来实现并发执行. 二.基本知识 1.进程与线程:进程作为操作系统执行程序的基本单位,拥有应用程序的资源,进程包含线程,进程的资源被线程共享,线程不拥有资源. 2.前台线程和后台线程:通过Thread类新建线程默认为前台线程.当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常. 3.挂起(Suspend)和唤醒(Resume):由于线程的执行

C++——多线程编程(一)std::thread

(一)与C++11多线程相关的头文件 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是< atomic> ,< thread>,< mutex>,< condition_variable>和< future>. ?< atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数. ?< thread>