精华内容
下载资源
问答
  • 消息传递机制(通讯)
    千次阅读
    2018-07-19 15:12:38

    一 同步和异步(线程)

    在维基百科中的释义是:在计算机编程中,异步,指的是独立于主程序流发生的事件,以及处理该事件的方式。这些可能是“外部”事件,例如信号的到达,或由程序发起的操作,该操作与程序同时/并发的执行,而程序不需要阻塞的等待结果。异步的输入(input)/输出(output)是导致异步的一个例子,让程序向存储或网络设备发出命令,而处理器继续执行程序。这样做提供了一定程度的并行性。

    在一个编程接口中处理异步的一种常见方法是提供子程序(方法、函数),这些子程序(方法、函数)返回给它们的调用者一个对象,有时称为future或promise,用来表示正在进行的事件。这样的对象通常会伴随着一个同步操作,直到操作完成为止。一些编程语言,如Cilk,具有用于表示异步过程调用的特殊语法。

    当您同步执行某个操作时,您需要等待它完成,然后再执行另一个任务。当您异步执行某个任务时,您可以在完成另一个任务之前继续执行

    也就是说,在计算机上下文中,这意味着在另外一个“线程”上执行一个过程或任务。线程是作为工作单元存在的一系列命令(代码块)。操作系统可以管理多个线程,并在切换到另一个线程之前,为其分配一段处理器时间使它可以做一些工作。而在核心上(指的是CPU的核心),一个处理器可以简单地执行一个命令,它不需要同时做两件事。操作系统通过将时间片分配给不同的线程来模拟这个过程。 
    现在,如果您使用多个内核/处理器的话,那么事情实际上可能同时发生。操作系统可以将时间分配给第一个处理器上的一个线程,然后将同样的时间块分配给另一个处理器上的另一个线程。所有这些都是关于允许操作系统管理任务的完成,而您可以在代码中继续执行其他任务。

    同步(单线程):

    1 thread ->   |<---A---->||<----B---------->||<------C----->|
    •  

    同步(多线程):

    thread A -> |<---A---->|   
                            \  
    thread B ------------>   ->|<----B---------->|   
                                                  \   
    thread C ---------------------------------->   ->|<------C----->| 
    •  

    异步 (单线程):

             A-Start ------------------------------------------ A-End   
               | B-Start -----------------------------------------|--- B-End   
               |    |      C-Start ------------------- C-End      |      |   
               |    |       |                           |         |      |
               V    V       V                           V         V      V      
    1 thread->|<-A-|<--B---|<-C-|-A-|-C-|--A--|-B-|--C-->|---A---->|--B-->| 
    • 比较难理解,可以理解为一个人边吃放边看电视,同时进行(个人观点)

    异步 (多线程):

    thread A ->     |<---A---->|
     thread B ----->     |<----B---------->| 
     thread C --------->     |<------C--------->|
    •  

    1.上图中的“|”标识的时间切片 
    2.“<” 和 “>”标识的是过程或者任务的开始于结束

    二 同步传输/异步传输

    1.异步传输

    通常,异步传输是以字符为传输单位,每个字符都要附加 1 位起始位和 1 位停止位,以标记一个字符的开始和结束,并以此实现数据传输同步。所谓异步传输是指字符与字符(一个字符结束到下一个字符开始)之间的时间间隔是可变的,并不需要严格地限制它们的时间关系。起始位对应于二进制值 0,以低电平表示,占用 1 位宽度。停止位对应于二进制值 1,以高电平表示,占用 1~2 位宽度。一个字符占用 5~8位,具体取决于数据所采用的字符集。例如,电报码字符为 5 位、ASCII码字符为 7 位、汉字码则为8 位。此外,还要附加 1 位奇偶校验位,可以选择奇校验或偶校验方式对该字符实施简单的差错控制。发送端与接收端除了采用相同的数据格式(字符的位数、停止位的位数、有无校验位及校验方式等)外,还应当采用相同的传输速率。典型的速率有:9 600 b/s、19.2kb/s、56kb/s等。

    异步传输又称为起止式异步通信方式,其优点是简单、可靠,适用于面向字符的、低速的异步通信场合。例如,计算机与Modem之间的通信就是采用这种方式。它的缺点是通信开销大,每传输一个字符都要额外附加2~3位,通信效率比较低。例如,在使用Modem上网时,普遍感觉速度很慢,除了传输速率低之外,与通信开销大、通信效率低也密切相关。

    2. 同步传输

    通常,同步传输是以数据块为传输单位。每个数据块的头部和尾部都要附加一个特殊的字符或比特序列,标记一个数据块的开始和结束,一般还要附加一个校验序列 (如16位或32位CRC校验码),以便对数据块进行差错控制。所谓同步传输是指数据块与数据块之间的时间间隔是固定的,必须严格地规定它们的时间关系。

    三 阻塞 与非阻塞

    阻塞与非阻塞可以理解为“等待状态”。 
    阻塞就是在调用结果返回之前,线程一直在等待结果。 
    非阻塞则是在不能立刻得到结果之前,线程不会一直等待结果。 
    还是以逛淘宝为例,在等待客服回复时,阻塞相当于你的精力一直放在与客服聊天的页面上,客服不回复爷爷就不走了。非阻塞就相当于你还是有一部分精力放在与客服聊天的页面上,不过同时你也可以干其他的事情(注意是可以,你也可以不干其他任何事,就坐在那里发呆)

    阻塞与非阻塞与是否同步异步无关。就上面淘宝的例子——同步阻塞:等待客服的同时,不干任何事情,也就是说你坐在电脑面前发呆、浪费时间;同步非阻塞:先做一会儿其他的事情,再回头看客服有没有回复,没有的话再做一会儿其他的事情在回来看;异步阻塞:在客服回复之前,你明明可以离开聊天页面去干别的事情,但你丫非要等客服回复,相当于在异步等待。异步非阻塞:在客服回复之前,离开聊天页面去干别的事情,直到客服回复你才回去继续聊天。

    从原理上讲异步也是可以有阻塞和非阻塞的,但实际上对异步讨论阻塞、非阻塞意义不大,一般阻塞和非阻塞是针对同步的。

    四 消息是透传

    1、透传:指与传输网络的介质、调制解调方式、传输方式、传输协议无关的一种数据传送方式

    这就好比快递邮件,邮件中间有可能通过自行车、汽车、火车、飞机的多种组合运输方式到达您的手上,但您不用关心它们中间经历了哪些。

     

    2、为什么要透传呢?
    透传一般都是用来读取远程的串口数据。例如:网吧内每个上网者都要刷身份证才能上网,但身份证数据库不可能放在每个网吧内。所以就将读卡器的串口数据通过透传回传到公安局,在公安局的平台上来比对身份证号码。

     

    更多相关内容
  • windows消息传递机制详解

    万次阅读 多人点赞 2018-01-31 18:06:11
    Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照...
                林炳文Evankaka 原创作品。转载请注明出处 http://blog.csdn.net/evankaka

         Windows是一个消息(Message)驱动系统。Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照到达的先后派对,依次处理(一些系统消息除外),这样可能使一些实时外部事件得不到及时处理。

          Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视化的交互方式,窗口是总是在某个线程(Thread)内创建的。Windows系统通过消息机制来管理交互,消息(Message)被发送,保存,处理,一个线程会维护自己的一套消息队列(Message Queue),以保持线程间的独占性。队列的特点无非是先进先出,这种机制可以实现一种异步的需求响应过程。

    目录:

    1、消息

    2  、消息类型

    3 、消息队列(Message Queues)

    4 、队列消息(Queued Messages)和非队列消息(Non-Queued Messages)

    5 、PostMessage(PostThreadMessage), SendMessage

    6 、GetMessage, PeekMessage

    7 、TranslateMessage, TranslateAccelerator

    8、(消息死锁( Message Deadlocks)

    9、BroadcastSystemMessage

    10、消息的处理

    11、MFC的消息映射

    12、消息反射机制


    1、消息

        消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键

    都会使Windows发送一个消息给应用程序。

        消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来

    说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,它在

    Windows中声明如下: 
    typedef struct tagMsg 

    HWND hwnd;          // 接受该消息的窗口句柄 
    UINT message;         // 消息常量标识符,也就是我们通常所说的消息号 
    WPARAM wParam;     // 32位消息的特定附加信息,确切含义依赖于消息值 
    LPARAM lParam;       // 32位消息的特定附加信息,确切含义依赖于消息值 
    DWORD time;         // 消息创建时的时间 
    POINT pt;             // 消息创建时的鼠标/光标在屏幕坐标系中的位置 
    }MSG; 
        消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也
    产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体,改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。

    2、消息类型

    1) 系统定义消息(System-Defined Messages)
            在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类: 
    1> 窗口消息(Windows Message)
           与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。 
    如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL... 
    2> 命令消息(Command Message)
            与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。 
    WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型 
    3> 控件通知(Notify Message)
            控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。 
    2) 程序定义消息(Application-Defined Messages)
            用户自定义的消息, 对于其范围有如下规定: 
    WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10) 
    WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4) 
    RegisterWindowMessage: 0xC000-0xFFFF

    3、消息队列(Message Queues)

     Windows中有两种类型的消息队列 
    1) 系统消息队列(System Message Queue)
            这是一个系统唯一的Queue,设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中等待处理 
    2) 线程消息队列(Thread-specific Message Queue)
            每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理. 
    注意: 线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。

    4、队列消息(Queued Messages)和非队列消息(Non-Queued Messages)

    1)队列消息(Queued Messages)
            消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理  、如鼠标,键盘消息。 
    2) 非队列消息(NonQueued Messages)
            消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理  如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED 
    注意: postMessage发送的消息是队列消息,它会把消息Post到消息队列中; SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理.

    队列消息和非队列消息的区别
            从消息的发送途径来看,消息可以分成2种:队列消息和非队列消息。消息队列由可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
         对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
         一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。

          非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,例如下面我们要谈到的函数。

    5 、PostMessage(PostThreadMessage), SendMessage
            PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。 
            SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。

    PostMessage(异步)和SendMessage(同步)的区别

    a、 PostMessage 是异步的,SendMessage 是同步的。

             PostMessage 只把消息放到队列,不管消息是不是被处理就返回,消息可能不被处理;

            SendMessage等待消息被处理完了才返回,如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息的返回。

    b、 同一个线程内:

              SendMessage 发送消息时,由USER32.DLL模块调用目标窗口的消息处理程序,并将结果返回,SendMessage 在同一个线程里面发送消息不进入线程消息队列;PostMessage 发送的消息要先放到消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。

    c、不同线程:

                 SendMessage 发送消息到目标窗口的消息队列,然后发送消息的线程在USER32。DLL模块内监视和等待消息的处理结果,直到目标窗口的才处理返回,SendMessage在返回之前还需要做许多工作,如响应别的线程向它发送的SendMessage().PostMessge() 到别的线程的时候最好使用PostThreadMessage   代替。PostMessage()的HWND 参数可以为NULL,相当于PostThreadMessage() + GetCrrentThreadId.

    d、系统处理消息。

           系统只处理(marshal)系统消息(0--WM_USER),发送用户消息(用户自己定义)时需要用户自己处理。

            使用PostMessage,SendNotifyMessage,SendMessageCallback等异步函数发送系统消息时,参数不可以使用指针,因为发送者不等待消息的处理就返回,接收者还没有处理,指针就有可能被释放了,或则内容变化了。

    e、在Windows 2000/XP,每个消息队列最多只能存放一定数量的消息,超过的将不会被处理就丢掉。系统默认是10000;:[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows] USERPostMessageLimit

    6 、GetMessage, PeekMessage
    PeekMessage会立即返回    可以保留消息 
    GetMessage在有消息时返回  会删除消息

    PeekMessage和GetMessage函数的主要区别有:
            a. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
            b. 如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
            c、GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。
    我们也可以说,PeekMessage是一个具有线程异步行为的函数,不管消息队列中是否有消息,函数都会立即返回。而GetMessage则是一个具有线程同步行为的函数,如果消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。
    如果消息队列中没有消息,PeekMessage总是能返回,这就相当于在执行一个循环,如果消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能因为消息队列为空而进入死循环。

    联系:

            在Windows的内部,GetMessage和PeekMessage执行着相同的代码,Peekmessage和Getmessage都是向系统的消息队列中取得消息,并将其放置在指定的结构。

    区别:

    PeekMessage:有消息时返回TRUE,没有消息返回FALSE 

    GetMessage:有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。

    GetMessage:取得消息后,删除除WM_PAINT消息以外的消息。

    PeekMessage:取得消息后,根据wRemoveMsg参数判断是否删除消息。PM_REMOVE则删除,PM_NOREMOVE不删除。

    The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.

    不能用PeekMessage从消息队列中删除WM_PAINT消息,从队列中删除WM_PAINT消息可以令窗口显示区域的失效区域变得有效(刷新窗口),如果队列中包含WM_PAINT消息程序就会一直while循环了。

    7 、TranslateMessage, TranslateAccelerator
             TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。 
            TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息, 然后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理, 处理完后才会返回。

    8、(消息死锁( Message Deadlocks)
    假设有线程A和B, 现在有以下下步骤 
            1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回 
            2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程中,B也向线程A SendMessgae,然后等待从A返回。  因为此时, 线程A正等待从线程B返回, 无法处理B发来的消息, 从而导致了线程A,B相互等待, 形成死锁。多个线程也可以形成环形死锁。 
    可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。

    9、BroadcastSystemMessage
            我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等, 
    BroadcastSystemMessage这个API可以对以上系统组件发送消息。

    10、消息的处理
            接下来我们谈一下消息的处理,首先我们来看一下VC中的消息泵:

    while(GetMessage(&msg, NULL, 0, 0))
    {
           if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))

                TranslateMessage(&msg);
                DispatchMessage(&msg);
           }
    }

    TranslateMessage(转换消息):

            用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。

    TranslateMessage函数

            用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

    也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。

    DispatchMessage(分派消息):

    把 TranslateMessage转换的消息发送到窗口的消息处理函数,此函数在窗口注册时已经指定。

            首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。
    然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个 WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。    
    处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则 GetMessage返回0,从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的 WM_DESTROY消息中调用。

    11、MFC的消息映射
             使用MFC编程时,消息发送和处理的本质和Win32相同,但是,它对消息处理进行了封装,简化了程序员编程时消息处理的复杂性,它通过消息映射机制来处理消息,程序员不必去设计和实现自己的窗口过程。
    说白了,MFC中的消息映射机制实质是一张巨大的消息及其处理函数对应表。消息映射基本上分为两大部分:
    在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),它放在类的末尾,是一个public属性的;与之对应的是在实现部分(.cpp)增加了一个消息映射表,内容如下:
    BEGIN_MASSAGE_MAP(当前类,当前类的基类)
    //{{AFX_MSG_MAP(CMainFrame)
    消息的入口项
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    但是仅是这两项还不足以完成一条消息,要是一个消息工作,必须还有以下3个部分去协作:
    1、在类的定义中加入相应的函数声明;
    2、在类的消息映射表中加入相应的消息映射入口项;
    3、在类的实现中加入相应的函数体;
    消息的添加
    (1)、利用Class Wizard实现自动添加
            在菜单中选择View -&gt; Class Wizard激活Class Wizard,选择Message Map标签,从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中,选取类的名称。此时,Messages列表框显示该类的可重载成员函数和窗口消息。可重载成员函数显示在列表的上部,以实际虚构成员函数的大小写字母来表示。其他为窗口消息,以大写字母出现。选中我们要添加的消息,单击Add Funtion按钮,Class Wizard自动将该消息添加进来。
    有时候,我们想要添加的消息在Message列表中找不到,我们可以利用Class Wizard上Class Info标签以扩展消息列表。在该页中,找到Message Filter组合框,通过它可以改变首页中Messages列表框中的选项。
    (2)、手动添加消息
     如果Messages列表框中确实没有我们想要的消息,就需要我们手工添加:
            1)在类的.h文件中添加处理函数的声明,紧接着在//}}AFX_MSG行之后加入声明,注意,一定要以afx_msg开头。
    通常,添加处理函数声明的最好的地方是源代码中Class Wizard维护的表的下面,在它标记其领域的{{ }}括弧外面。这些括弧中的任何东西都有可能会被Class Wizard销毁。
            2)接着,在用户类的.cpp文件中找到//}}AFX_MSG_MAP行,紧接在它之后加入消息入口项。同样,也放在{{ }}外面。
            3)最后,在该文件中添加消息处理函数的实体。
    对于能够使用Class Wizard添加的消息,尽量使用Class Wizard添加,以减少我们的工作量;对于不能使用Class Wizard添加的消息和自定义消息,需要手动添加。总体说来,MFC的消息编程对用户来说,相对比较简单,在此不再使用实例演示。
            12、消息反射机制
    什么叫消息反射?
             父窗口将控件发给它的通知消息,反射回控件进行处理(即让控件处理这个消息),这种通知消息让控件自己处理的机制叫做消息反射机制。
            通过前面的学习我们知道,一般情况下,控件向父窗口发送通知消息,由父窗口处理这些通知消息。这样,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码。很明显,这对代码的维护和移植带来了不便,而且,明显背离C++的对象编程原则。
            从4.0版开始,MFC提供了一种消息反射机制(Message Reflection),可以把控件通知消息反射回控件。具体地讲,对于反射消息,如果控件有该消息的处理函数,那么就由控件自己处理该消息,如果控件不处理该消息,则框架会把该消息继续送给父窗口,这样父窗口继续处理该消息。可见,新的消息反射机制并不破坏原来的通知消息处理机制。
    消息反射机制为控件提供了处理通知消息的机会,这是很有用的。如果按传统的方法,由父窗口来处理这个消息,则加重了控件对象对父窗口的依赖程度,这显然违背了面向对象的原则。若由控件自己处理消息,则使得控件对象具有更大的独立性,大大方便了代码的维护和移植。
            实例M8:简单地演示MFC的消息反射机制。(见附带源码 工程M8)
         开VC++ 6.0,新建一个基于对话框的工程M8。
    在该工程中,新建一个CMyEdit类,基类是CEdit。接着,在该类中添加三个变量,如下:
    private:
    CBrush m_brBkgnd;
    COLORREF m_clrBkgnd;
    COLORREF m_clrText;
    在CMyEdit::CMyEdit()中,给这三个变量赋初值:
    {
    m_clrBkgnd = RGB( 255, 255, 0 );
    m_clrText = RGB( 0, 0, 0 );
    m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );
    }
    打开ClassWizard,类名为CMyEdit,Messages处选中“=WM_CTLCOLOR”,您是否发现,WM_CTLCOLOR消息前面有一个等号,它表示该消息是反射消息,也就是说,前面有等号的消息是可以反射的消息。
    消息反射函数代码如下:
    HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor)
    {
        // TODO: Change any attributes of the DC here
        pDC-&gt;SetTextColor( m_clrText );//设置文本颜色
        pDC-&gt;SetBkColor( m_clrBkgnd );//设置背景颜色
         //请注意,在我们改写该函数的内容前,函数返回NULL,即return NULL;
        //函数返回NULL将会执行父窗口的CtlColor函数,而不执行控件的CtlColor函数
        //所以,我们让函数返回背景刷,而不返回NULL,目的就是为了实现消息反射
        return m_brBkgnd; //返回背景刷
    }
    在IDD_M8_DIALOG对话框中添加一个Edit控件,使用ClassWizard给该Edit控件添加一个CMyEdit类型的变量m_edit1,把Edit控件和CMyEdit关联起来。

    林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

    展开全文
  • Golang和Erlang消息传递机制对比

    千次阅读 2015-12-11 15:25:22
    上一篇文章介绍了 Go 和 Erlang 在调度器的实现,本文将简要介绍这两种并发语言的消息传递机制简要对比Erlang和Go虽然在实现及功能上差异较大,但是都支持高并发的轻量级用户任务(Erlang的轻量进程,Go的Goroutine...

    上一篇文章介绍了 Go 和 Erlang 在调度器的实现,本文将简要介绍这两种并发语言的消息传递机制

    简要对比

    Erlang和Go虽然在实现及功能上差异较大,但是都支持高并发的轻量级用户任务(Erlang的轻量进程,Go的Goroutine), 并且都采用了消息传递的方式作为任务间交互的方式。

    在Erlang中,采用了一种比较纯粹的消息传递机制,进程间几乎没有任何形式的数据共享,只能通过彼此间发送消息进行通信; 而Go虽然是基于共享内存的,但是也必须通过消息传递来进行共享数据的同步。 可以说消息传递机制是两种语言任务间交互的首要方式。

    但是在具体实现中,鉴于两种语言的差异,也表现为不同的形式:

    • 在Erlang中,进程之间以彼此的Pid作为标识进行消息的发送,一切数据都仅可以消息的形式在进程间复制
    • 在Go中,不同的Goroutine间通过共享的channel进行通信,由于Go本质上是建立在共享存储模型上的,因此全局变量、参数甚至是一部分栈变量都是可以共享的,通信一般控制在较小的规模,仅用来保证共享的同步语义

    下面将分别就Erlang和Go各自实现分别进行分析介绍。

    Erlang中的消息传递机制

    语法

    消息传递是Erlang语言并发机制的基础。在Erlang中,主要包含以下并发原语:

    • 创建一个新的并发轻量进程,用于执行函数Fun,并返回其进程标识符Pid:
    Pid = spawn(Fun)
    
    • 向标识符为Pid的进程发送消息(注: 由于Pid ! M的返回值是消息M本身,因此可以用类似Pid1 ! Pid2 ! Pid3 ! … M的语法来向多个进程发送同一个消息):
    Pid  ! Message
    
    • receive ... end 来接收一个发送给当前进程的消息,语法如下(注: 当一个进程接收到一个消息时,依次尝试与Pattern1(及Guard1),Pattern2(及Guard2), … 进行模式匹配,若成功,则对相应的Expressions求值,否则继续后续匹配):
    receive
      Pattern1 [when Guard1] ->
          Expressions1;
      Pattern2 p[when Guard2] ->
          Expressions2;
      ... ...
    end
    
    • 对于接收操作,我们还可以为其设置一个超时控制,一旦超过某个预设的时长仍没有消息到达,则执行相应的超时操作,语法如下:
    receive
      Pattern1 [when Guard1] ->
          Expressions1;
      Pattern2 p[when Guard2] ->
          Expressions2;
            ... ...
    after Time ->
      ExpressionTimeout
    end
    
    • 可以建立一个只包含超时的接收操作,事实上就相当于一个延时操作:
    sleep(T) ->
      receive
          after T ->
              true
      end
    

    以上就是Erlang中基本的消息传递的接口。

    内部实现

    相对于Go而言,Erlang的发展历史更加悠久,因此代码复杂程度要大大高于Go这门新型语言。 因此在分析过程中,主要还是以介绍实现机制为主,具体的数据结构及源代码实现就不作过分细致的剖析了。

    Erlang的BEAM解释器的代码位于 otp/erts/emulator/beam/ 路径下, 其中与Send相关的代码位于 otp/erts/emulator/beam/erl_message.c 中, 而与Receive相关的代码则位于 otp/erts/emulator/beam/beam_emu.c 中。

    之所以不在一处实现,是因为Erlang把接收操作作为一种BEAM基本指令来实现,而发送操作则以内部函数的方式实现。

    在具体实现中,Erlang采用了消息Copy的方法实现消息传递——消息从发送进程的堆拷贝到接收进程的堆:

    • 在发送时,如果接收进程正在另一个调度器上执行,或者有其他并发的进程也在向其发送消息时,本次发送就存在竞争风险,不能直接完成
    • 发送者会在堆上为接收进程分配一个临时区域并将消息拷贝到该区(该内存区将在后续进行垃圾收集时合并到接收进程的堆空间)
    • 拷贝完成后,会将指向这块新区域的指针链入接收进程的消息队列
    • 如果接收进程正处于阻塞态,则将其唤醒并添加到就绪队列中

    在SMP版Erlang虚拟机中,进程的消息队列由两部分组成—— 一个公共队列和一个私有队列。 公共队列用于接收其他进程发送的消息,通过互斥锁加以保护;私有队列用来减少对锁的争用: 接收进程首先在接收队列中查找符合匹配的消息,如果没有,再从公共队列中复制消息到私有队列中。

    在非SMP的Erlang虚拟机中,进程只有一个消息队列。

    接收进程发现当前任务队列上没有匹配的消息后,会跳转执行wait_timeout: 设置一个定时器并阻塞在该定时器上—— 当该定时器触发或者有新消息到来时,都会唤醒接收进程。

    由于进程的消息缓冲是以队列形式维护的,因此从发送进程角度来看,可以认为消息缓冲的大小是无限的, 因此Send操作一般不会阻塞,这点注意与后面要将的Go消息传递机制相区别。 这也就是为什么Erlang中仅对Receive操作提供超时响应机制的原因了!

    高级特性

    与Go主要面向SMP服务器应用不同,Erlang是一种面向分布式集群环境的编程语言, 因此消息传递除了支持本地进程间的通信外,还支持分布式环境的进程间通信。

    基本流程是:

    • 首先通过Pid(或在分布式环境上的等价概念)查询“名字服务”
    • 找到该远程进程所在的远程主机地址信息,进而通过套接字进行后续发送操作
    • 远程Erlang虚拟机进程接收到消息,再进一步分析,将其派发到指定的进程消息队列中

    进一步的实现细节会涉及Erlang分布式处理的机制,这里就不展开分析了,待后续单开主题讨论。

    Go中channel机制介绍

    语法

    在Go中,channel结构是Goroutine间消息传递的基础,属于基本类型,在runtime库中以C语言实现。 Go中针对channel的操作主要包括以下几种:

    • 创建:ch = make(chan int, N)
    • 发送:ch <- l
    • 接收:l <- ch
    • 关闭:close(ch)

    另外,还可以通过select语句同时在多个channel上进行收发操作,语法如下:

    select {
      case ch01 <- x:
          ... ... /* do something ... */
      case y <- ch02:
          ... ... /* do something ... */
      default:
          ... ... /* no event ... */
    }
    

    此外,基于select的操作tai还支持超时控制,具体的语法示例如下:

    select {
      case v := <- ch:
          ... ...
      case <- time.After(5 * time.Second):
          ... ...
    }
    

    尽管Go以消息传递作为Goroutine间交互的主要方式,但是基于channel的通信又必须依赖channel引用的共享才能得以实现, 因此Go语言绝不是一种纯粹的消息传递语言。 一般而言,channel的引用可以通过以下几种方式在不同Goroutine间共享:

    • “父Goroutine” 的栈变量,通过用Go语句创建Goroutine时的参数进行传递
    ch = make (chan int)
    go fn (ch)
    l <- ch
    
    • “父Goroutine” 的栈变量,Go创建的Goroutine以闭包作为执行函数,栈变量自动共享
    ch = make (chan int)
    go func () { i = 1; chan <- i; } ()
    x <- chan
    
    • 由于Go的垃圾收集器认为channel的引用只能在栈上,因此一般不用全局的引用进行共享

    另外,在创建channel时,还可以指定是否采用buffer及buffer的大小,默认buffer为0 。 当buffer大于0时,可以进行异步的消息传递:接收方只有在当前buffer为空时才阻塞,而发送方则只有在buffer满时才阻塞。 详细情形将在后面介绍。

    内部实现

    核心数据结构

    Go中channel的实现代码在src/pkg/runtime/chan.c中,其核心数据结构如下 (对于gccgo,其实现代码在libgo/runtime/chan.c中,由于使用不同的C编译器及语法,因而数据结构实现略有不同):

    struct Hchan
    {
      uintgo   count;        // total data in q
      uintgo   dataqsize;    // size of the circular q
      uint16   elemsize;
      uint16   pad;      // ensures proper alignment of the buffer that follows Hchan in memory
      bool    closed;
      Alg*  elemalg;  // interface for element type
      uintgo   sends;        // send index
      uintgo   recvx;        // receive index
      WaitQ    recvq;        // list of recv waiters
      WaitQ    sendq;        // list of send waiters
      Lock;
    }
    

    我们可以按照表示属性还是表示状态将Hchan的内部成员进行分类并逐一分析。

    表示channel属性的成员如下,这些成员在用make进行初始化后确定,并且在后续操作中不会变化:

    struct Hchan
    {
      ... ...
      uintgo   dataqsize;
      uint16   elemsize
      uint16   pad;
      Alg*  elemalg;
      ... ...
    }
    

    其中,

    • dataqsize是前文所说的buffer的大小,即make(chan T, N)中的N
    • elemsize 是channel对应单个元素的大小,pad主要用于表示该类型元素的内存对齐边界
    • elemalg 也对应于类型属性,主要是一些列具体操作该类型的函数指针,如copy、hash、equal及print等接口

    另外一类成员则用来表示当前channel的状态,是随着程序运行而发生变化的:

    struct Hchan
    {
      ... ...
      bool    closed;
    
      uintgo   count;
      uintgo   sendx;
      uintgo   recvx;
    
      WaitQ    recvq;
      WaitQ    sendq;
      Lock;
      ... ...
    }
    

    我们按功能将状态信息分为三类:

    • closed 表示当前channel是否处于关闭状态,在make创建后,此域被置为false,即channel处于打开状态;通过调用close将其置为true,channel关闭,之后的任何send/recv操作都会失败
    • countsendxrecvx一同构成了一个环形buffer的状态域,其中count表示当前buffer中的占用数量,sendxrecvx分别表示当前的发送位置和接收位置,注意,count的大小不能超过dataqsize。另外,还有一个“隐形”的域,即真正暂存数据的buffer空间。它并不在Hchan的域中,而是在make创建channel时被地位在紧随Hchan对象后面的相应大小的内存区域,具体代码通过chanbuf(c, i)这个宏来访问对应的区域。
    • 最后一部分是两个等待队列,分别用来存放在发送和接收过程中被阻塞的任务,由于发送接收必须是互斥操作,因此必须有相应的锁类型(注:这里用到了Plan9的C扩展语法,用内部结构体表示一种类似继承的语义

    Send/Recv实现分析

    我们以Send操作为例,介绍在单个channel上的具体流程,基于select语句的多channel操作将在下一节进行介绍。

    首先,我们来看Send操作在Go runtime中的接口定义:

    void runtime·chansend(ChanType *t, Hchan *c, byte *ep, bool *pres, void *pc);
    

    解释一下主要参数: * ChanType *t : 是channel对应的数据类型 * Hchan * c: 就是当前操作的channel的指针 * byte *ep: 待发送数据的缓冲区指针,由于Send操作时一个统一的接口,因此使用byte型指针 * bool *pres : 这个真针不为NIL时,Send为非阻塞式操作,如果不能发送,就直接返回并设* pres = false * void * pc : 当前操作的PC

    在介绍具体函数之前,我们需要先介绍一下SudoG这一结构,该结构是前面介绍的等待队列WaitQ类型的节点元素类型,其定义如下:

    struct SudoG
    {
      G*        g;            // g and selgen constitute
      uint32   selgen;       // a weak pointer to g  
      SudoG*    link;
      int64    releasetime;
      byte* elem;     // data elment
    }
    

    这个结构比较简单,g就是当前阻塞的Goroutine,elem是响应操作调用时传入的数据缓冲指针(*ep), link域就是单链表的下一项,releasetime用于系统性能统计(profiling)。 最后的selgen在select操作时被用到,后面详述。

    下面具体分析该函数流程:

    1. 首先,在判断c非空,及一系列初始化操作后,当前任务尝试获得c的锁
    2. 成功锁定当前c后,先判断是否带buffer,若有buffer,则进入async模式,否则继续
    3. 若c已close,则返回(从close态返回是实现在channel上的Range操作的关键!
    4. 否则,尝试从c->recvq中取出一个任务(SudoG类型),如果返回非空,则该任务是一个正在阻塞状态的接收任务,执行:
      • 将当前的ep指针指向的数据copy到取出的SudoG类型元素的elem指针区域
      • SudoG中的releasetime设为当前系统tick值
      • 调用runtime·ready唤醒对应的g,本次Send操作完成
    5. 如果返回NIL,表明当前没有等待的接收任务。这时需要判断pres指针是否为空。若不为空,将其所指向的bool变量设为false,并退出
    6. 反之,则当前任务挂起:
      • 在当前栈上新建SudoG对象,并用当前gep初始化
      • 将该SudoG链入c->sendq
      • 调用runtime·park将自己阻塞,同时释放c的锁
    7. 当前g对象被唤醒后,首先判断是否是c已关闭,进而根据releasetime值唤醒超时事件,最后返回

    对于带buffer的channel,将进入async模式进行处理:

    1. 首先判断当前缓冲区是否已满,若满,则阻塞,过程与上面不带buffer的情况类似
    2. 若缓冲区不满,则将ep指针指向的区域按c元素大小复制到环形缓冲区,并修改缓冲区内部状态

    以上就是Send操作的全部实现,其中有一个比较重要的函数,即从等待队列中取出一个元素的操作dequeue(WaitQ *)还没有仔细分析。 主要原因是其涉及到下一节要介绍的select机制,因此退后分析。至于其反操作enqueue(WaitQ *)则就是一个简单的链表尾部插入,不需要额外分析。

    对于Recv操作,其步骤基本上与Send类似,仅仅是操作的队列和copy的方向有别,这里不再赘述。

    为了更好的理解上述操作,我做了一组简单的示意图,以“异步channel”(即带缓冲区的channel)为例,来描述这一过程:

    • 任务 Go#1 向channel发送数据#1,此时channel的缓冲区是空的,并且没有正在阻塞的任务,则数据#1被拷贝到缓冲区,缓冲区指针后移,Go#1 完成操作退出(非阻塞),如图1所示: 

    • 当channel的缓冲区被填满后,当另一个任务 Go#4 再进行发送操作时,就只能将自己及待发送的数据#4以某种方式链入channel的发送等待队列中,并挂起(阻塞),如图2所示:

    • 这时,任务 Go#5 发起针对这个channel的Recv操作,首先将从缓冲区读取并拷贝数据,直到缓冲区为空:

    • 假设此时缓冲区已空,又有一个任务 Go#8 发起一个Recv操作,它将从发送队列取出一个阻塞的数据,即之前Go#4发送的#4数据,然后读取并拷贝该数据,并将Go#4唤醒:

    Select操作

    介绍

    Go在语言级提供了一个select语句用来支持同时在多个channel上进行操作的功能,很类似于Unix系统上的select系统调用,只不过后者操作的类型时文件描述符而已。

    与Unix的select操作类似,任意一个注册的channel操作返回则select操作亦返回。 Unix的select通过逐一测试文件描述符集合判断是当前操作,而Go则通过不同的case语句指示当前响应的channel操作。

    两外,二者也都支持超时控制。但是Unix的超时控制是建立在分时调度机制上的,更加可靠; 而Go的调度器如前所述,是协作式的,因此超时控制不可能精确的实现。

    主要数据结构

    Go语言将select操作作为语言一级支持,为此额外定义了ScaseSelect两个结构体来表示一个select语句。

    • 我们先来简单的介绍一下Scase,其对应于select语句中的每个case/default块,其定义如下:
    struct Scase
    {
      SudoG    sg;           // must be first member (cast to Scase)
      Hchan*    chan;     // chan
      byte* pc;           // return pc
      uint16   kind;
      uint16   so;           // vararg of selected bool
      bool*    receivedp;    // pointer to received bool 
    }
    
    • Scase 结构说明:

      • 该类型可以认为是上节介绍的SudoG类型的派生类型,其“基类型”对象就是sg(因此注释里要求它必须位于起始位置)
      • chan就是当前这个case操作的channel指针
      • pc是这个case成功后的返回地址,在下节中揭晓其用法
      • kind是当前case的操作类型,可以是CaseRecvCaseSendCaseDefault之一
      • recievedp指针用来指示当前操作是否是非阻塞的,和之前介绍的情形类似
      • so这个变量比较有意思,它主要是用来作返回时的判断,在后面详述
    • 接下来介绍Select结构,其定义如下:

    struct Select
    {
      uint16   tease;        // total count of scare[]
      uint16   ncase;        // currently filled scare[]
      uint16*   pollorder;    // case poll order
      Hcan**    lockorder;    // channel lock order
      Scase    scase[1];  // one per case (in order of appearance )
    }
    
    • Select结构说明:
      • 该类型的中的tease表示该select语句中包含的case/default块的个数,由于select语句的case数在编译时刻就可以确定,因此Select被以定长方式实现
      • ncase表示当前已添加的case块数,虽然tcase在创建时就已知了,但每个case块却是在parse的过程中逐个添加的,因此需要一个描述当前大小的成员
      • pollorder是在case块列表上操作的顺序,考虑到性能因素,Go的每次select操作都采用了随机顺序访问每个case中的channel
      • lockorder 表示在当前select语句上所有channel的加锁顺序,由于select操作是一个互斥操作,并且需要同时获取所有channel的锁,因此必须保证加锁按照一个约定的顺序进行,以避免不同select间造成死锁。Go的实现是按照channel对应虚地址大小作为加锁的顺序
      • scase[1]及其后的连续内存区域是包含全部Scase的数组,大小在创建Select时就已知了,所以用固定大小数组存放

    代码生成

    我们知道,Go的parser遇到select语句时会将其翻译成对应的runtime库函数调用以实现其功能,那么这种映射是如何实现的呢?

    • 首先,在select开始时调用void runtime·newselect(int size, …)创建一个Select实例
    • 然后,每遇到一个case或default语句,就调用响应类型的操作向Select实例中添加一个Scase元素:
      • void runtime·selectsend(Select *sel, Hchan *c, void *elem, bool selected)
      • void runtime·selectrecv(Select *sel, Hchan *c, void *elem, bool selected)
      • void runtime·selectdefault*(Select *sel, bool selected)
    • 每次调用上面3个函数,都会将返回PC值记录在相应的Scase实例中
    • 最后,添加完全部case后,调用void * runtime·selectgo(Select ** selp)进入真正的Select操作(算法详见下节)
    • 一旦发现channel操作,则该函数返回对应case记录的返回地址,执行该case对应的函数块

    流程分析完了,是否觉得有点奇怪?问题出在哪儿?

    对了,就是在selectgo返回时,程序如何判断目前是在注册阶段Scase阶段返回的,还是从selectgo返回的呢?

    我们以runtime·selectsend操作为例解释,从Go语言的角度来看,这个函数的Go型接口定义如下:

    selectsend(del *byte, hchan chan<- any, elem *any) (selected bool);
    

    这下明白了吧?由于Google Go编译器采用了Plan9 风格的C编译器,使得该函数在Go程序看来实际上是返回了一个bool型的值selected, 该bool值即用来表明当前返回的状态,直接从selectsend返回还是从selectgo间接返回。 还记得刚才没有将明白的Scase中的so成员吗?你现在可以把它理解成一个到selected的指针(实际上是一个到该指针的偏移地址)。

    需要注意的是,这里之所以可以这么实现,主要是Google Go编译时甚至使用了一套自己定义的C编译链接工具,对参数和返回值在栈空间的分配做了特别的约定。 而在gccgo中,由于采用gcc 编译,因此就必须按照较为传统的方式,由selectgo返回活跃的Scase的编号,然后再根据编号分别进行处理。

    下面列出一个Go中的select语句翻译为C语言后的形式,作为参考 (事实上Go编译器会直接将Go代码翻译成汇编语言,这里用C描述主要是为了简化描述):

    // Go 语言版代码片段
    ... ...
    select {
      case ch01 <- x:
          foo() /* do something of case #1 */
      case y <- ch02:
          bar() /* do something of case #2 */
      default:
          def() /* do something for default  */
    }
    ... ...
    
    // 对应C代码片段示意
    Select *sel = newselect(3);
    ... ...
    if (selectsend(sel, ch01, &x)) {
      foo() ; // do something of case #1 
    } else if (selectrecv(sel, ch02, &y)) {
      bar() ; // do something of case #2
    } else if (selectdefault(sel)) {
      def(); // do something of default
    } else {
      void (*rpc) (void);
      rpc = selectgo (&sel);
      (*rpc)(); //goto rpc;
    }
    ... ...
    

    算法实现

    经过了上面的介绍,我们大概了解了select语句的生成规则,现在将视线集中到其核心函数runtime·selectgo函数的实现上来。

    应该说该函数实际上类似于上节介绍的Send、Recv操作在多channel情况下的扩展,基本流程也比较类似。 但是需要特别注意以下几点:

    • 操作是互斥的,因此需要在进行操作前一次性获取全部待处理channel上的锁
    • select语句只要有一个channel响应即可返回,无需等待所有channel响应
    • 如果当前没有channel可以响应且不存在default语句,则当前g必须在所有channel的相应等待队列上挂起
    • 只能有一个响应的channel可以唤醒之前挂起的g
    • 另外,考虑到性能原因,select操作的顺序不一定按照程序中声明的顺序操作

    明确了上面的要点后,selectgo 的实现便呼之欲出了。

    1. 首先,确定poll操作顺序和加锁的顺序:poll按照随机排序,加锁则按照channel虚拟地址排序
    2. 然后进入加锁阶段,按照#1中的顺序对每个channel加锁
    3. 进入主循环,按照#1中计算的poll顺序依次遍历所有的channel,如果当前channel可以响应(对应等待队列不空或存在可用的buffer),则跳转到#7
    4. 所有channel都不可响应,返回default的返回PC;如果没有default语句,则进入阻塞态
    5. 根据操作类型将当前g依次加入每个channel的相应WaitQ中,调用runtime·park进入阻塞态,同时释放所有channel锁(注意顺序!
    6. 当前g被唤醒,说明有channel已响应,保存该channel引用,再次获得所有的锁,并从其他channel的等待队列中将当前g删除
    7. 完成响应的copy操作,释放所有锁,返回活跃channel对应Scase中记录的PC地址

    整个过程大体如下图所示:

    这样,selectgo就基本上介绍完了,但是还遗留了一个问题。某个任务调用select阻塞时,会将自身对应的SudoG添加到所有channel的等待队列上, 那么如果有多个channel在任务被唤醒并完成加锁前发生响应,该如何处理呢?

    要解决这个问题,就必须弄清楚chan.c中的dequeue操作是如何实现的,因为每个任务被唤醒前,必须经过dequeue从相应的等待队列上获得, 因此只要保证对于在Select语句中的操作,只能被dequeue成功返回一次即可。

    之前讲过,每个SudoG都包含一个selgen成员,其实在每个G中,也包含一个selgen成员。对于一个不在Select中的Send/Recv操作, 其随影的selgen置为常量NOSELGEN,可以被dequeue直接返回。 否则,在Select操作的#5中,将Gselgen值赋给SudoGselgen。 在进行dequeue操作时,一旦发现SudoGselgen不是NOSELGEN,则调用如下原子操作:

    SudoG *sgp = q->first
    ... ...
    if ( CAS(&sgp->g->selgen, sgp->g->selgen, sgp->selgen+2) )
      return sgp;
    else
    ... ...
    

    由 CAS 原子操作定义可知,该阻塞的 goroutine 尽可能被唤醒一次。

    初窥“超时控制”机制

    这里我们先简单介绍一下Go语言select中的超时控制机制,实际上,在Go中,定时控制并非为select语句所独有,而是一种通用的机制。 在这里我们仅针对select的例子简单的介绍一下Go的定时控制机制,后续有空时再作深入分析。

    回到开始时那个超时控制的例子:

    ... ...
    select {
      case v := <- ch:
                ... ...
      case <- time.After(5 * time.Second):
                ... ...
    }
    ... ...
    

    在第二个case语句中,我们调用了time.After()函数定义了一个5秒钟的定时器,我们来看看这个函数的原型定义:

    fund After(d Duration) <- chan Time {
      return NewTimer(d).C
    }
    

    这个函数实际上返回一个channel类型,并在定义的时间到时,向该channel发送一个Time型数据。

    了解了这些,上面的例子就被统一起来了,每个case仍然是针对channel的,如果ch在5秒内响应,则执行ch对应case的语句; 否则第二个case的channel响应,也就进入了超时的处理过程中。

    在Go的内部实现中,所有相关的定时操作都是通过time.Timertime.Ticker类型实现,它们内部都对应一个channel和一个runtimeTimer类型。 通过runtimeTimer可以在Go语言上下文中注册定时事件到runtime层,Go的runtime层存在一个后台任务(Groutine)来实现定时器事件唤醒功能。简单的说,这个后台任务的工作就是轮训系统中的定时器,一旦发现到期的定时器,就向其绑定的channel发送数据,则阻塞在该channel上的任务就被唤醒了。

    关于runtimeTimer的实现详情,请参看Go的源代码src/pkg/runtime/time.goc。 由于这部分本身和channel机制关联并不密切,后面有机会再单开主题讨论。

    总结

    通过上面分析,我们不难得出两种语言在消息传递上的区别与共性, 我总结了以下几点,请大家补充、指正。

    区别:

    • Go基于channel进行通信,而channel引用必须在任务间共享访问; Erlang利用Pid进行发送,而接收时仅根据消息内容区别后续处理的方式,过程中没有任何形式的数据共享

    • Go的Send/Recv操作都可能阻塞; Erlang一般仅有接收操作可能引起阻塞

    • Go的select可以同时等待多channel上的Send/Recv操作,并提供超时处理机制; Erlang的发送操作仅能每次针对一个进程,接收操作针对本进程的消息队列,也提供超时机制

    • Go的消息传递仅支持本机Goroutine间通信; Erlang消息传递支持在分布式环境上的进程间通信

    共性:

    • Go和Erlang的消息传递都提供了语法级支持,并且都是语言的重要组成部分
    • Go和Erlang的消息传递都是通过数据的Copy实现的,因此都强调“小消息,大计算”的处理方式

    结尾的思考

    Go语言的并发模型源自Hoare的CSP模型(Communicating Sequential Processes, 国内译为“通信顺序进程”,台湾译为“交谈循序程序”),可以被视为一种类似 Unix 管道的东西。 它的特点是每个任务程序的编写完全按照其执行的时序进行,方便编程及调试分析。

    与之相对的另一种并发编程模式就是基于异步消息及回调的模型,比如 libev 以及前阶段非常火的 Node.js, 这类模式的特点是调度以回调函数为单位,所有的消息发送都是异步的,程序员看到的就是消息类型及其对应的处理函数。

    我认为两种方式各有优缺点,Go的方案更符合人类思考问题的习惯,编程和调试效率较高;但由于需要保证高并发性,就要实现用户任务层的调度,例如采用协程(如Go、rust等),或是采用基于虚拟机的方案(如 Erlang、Lua等),这无疑增加了上下文切换的开销,同时也增加了runtime或虚拟机的实现难度。

    基于“异步消息/回调函数”的方案恰好相反,由于调度粒度变小为函数,导致实现上比较简单,顺序编程的固有性质使得系统开发者不需要考虑保护上下文或者类似的东西,每个回调就是依次被取出、派发、执行、返回,因此后端实现相对简单;反之,应用程序员就要小心的设计程序,考虑很多诸如功能划分、线程安全之类的细节,同时也给分析及调试程序带来了很大的问题。

    参考资料

    除了参考Go及Erlang的代码外,本文还参考了以下文献:

    展开全文
  • iOS中消息传递机制

    千次阅读 2016-03-19 19:21:13
    每个应用程序或多或少,都由一些松耦合的对象构成...本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么时机该选择使用什么机制。

    iOS中消息的传递机制

    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么时机该选择使用什么机制。

    虽然这一期的主题是关于Foundation Framework的,不过本文中还介绍了一些超出Foundation Framework(KVO和Notification)范围的一些消息传递机制,另外还介绍了delegation,block和target-action。

    大多数情况下,消息传递该使用什么机制,是很明确的了,当然了,在某些情况下该使用什么机制并没有明确的答案,需要你亲自去尝试一下。

    本文中,会经常提及接收者[recipient]和发送者[sender]。在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者。Core Data managed object context是notification的发送者,而获取这些notification的主体则是接收者。一个滑块(slider)是action消息的发送者,而在代码里面对应着实现这个action的responder就是接收者。对象中的某个属性支持KVO,那么谁修改这个值,谁就是发送者,对应的观察者(observer)则是接收者。

    可用的机制

    首先我们来看看每种机制的具体特点。在下一节中,我会结合一个流程图来介绍如何在具体情况下,选择正确的消息传递机制。最后,将介绍一些来自苹果Framework中的示例,并会解释在某种确定情况下为什么要选择固定的机制。

    KVO

    KVO提供了这样一种机制:当对象中的某个属性值发生了改变,可以对这些值的观察者做出通知。KVO的实现包含在Foundation里面,基于Foundation构建的许多Framework对KVO都有所依赖。要想了解更多关于如何使用KVO,可以阅读本期由Daniel写的的KVO和KVC文章。

    如果对某个对象中值的改变情况感兴趣,那么可以使用KVO消息传递机制。这里有两个要求,首先,接收者(会接收到值发生改变的消息)必须知道发送者(值将发生改变的那个对象)。另外,接收者同样还需要知道发送者的生命周期,因为在销毁发送者对象之前,需要取消观察者的注册。如果这两个要求都满足了,消息传递过程中可以是1对多(多个观察者可以注册某个对象中的值)。

    如果计划在Core Data对象上使用KVO,需要知道这跟一般的KVO使用方法有点不同。那就是必须结合Core Data的故障机制(faulting mechanism),一旦core data出现了故障,它将会触发其属性对应的观察者(即使这些属性值没有发生改变)。

    Notifications

    在不相关的两部分代码中要想进行消息传递,通知(notifacation)是非常好的一种机制,它可以对消息进行广播。特别是想要传递丰富的信息,并且不一定指望有谁对此消息关心。

    通知可以用来发送任意的消息,甚至包含一个userInfo字典,或者是NSNotifacation的一个子类。通知的独特之处就在于发送者和接收者双方并不需要相互知道。这样就可以在非常松耦合的模块间进行消息的传递。记住,这种消息传递机制是单向的,作为接收者是不可以回复消息的。

    delegate

    在苹果的Framework中,delegation模式被广泛的只用着。delegation允许我们定制某个对象的行为,并且可以收到某些确定的事件。为了使用delegation模式,消息的发送者需要知道消息的接收者(delegate),反过来就不用了。这里的发送者和接收者是比较松耦合的,因为发送者只知道它的delegate是遵循某个特定的协议。

    delegate协议可以定义任意的方法,因此你可以准确的定义出你所需要的类型。你可以用函数参数的形式来处理消息内容,delegate还可以通过返回值的形式给发送者做出回应。如果只需要在相对接近的两个模块之间进行消息传递,那么Delegation是一种非常灵活和直接方式。

    不过,过渡使用delegation也有一定的风险,如果两个对象的耦合程度比较紧密,相互之间不能独立存在,那么此时就没有必要使用delegate协议了,针对这种情况,对象之间可以知道相互间的类型,进而直接进行消息传递。例如UICollectionViewLayout和NSURLSessionConfiguration。

    Block

    Block相对来说,是一种比较新的技术,它首次出现是在OS X 10.6和iOS 4中。一般情况下,block可以满足用delegation实现的消息传递机制。不过这两种机制都有各自的需求和优势。
    当不考虑使用block时,一般主要是考虑到block极易引起retain环。如果发送者需要reatain block,而又不能确保这个引用什么时候被nil,这样就会发生潜在的retain环。
    假设我们想要实现一个table view,使用block替代delegate,来当做selection的回调,如下:

    self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) { 
        // handle selection ... 
    }; 

    上面代码的问题在于self retain了table view,而table view为了之后能够使用block,进而 retain了block。而table view又不能把这个引用nil掉,因为它不知道什么时候不在需要这个block了。如果我们保证不了可以打破这个retain环,而我们又需要retain发送者,此时block不是好的选择。

    NSOperation就可以很好的使用block,因为它能再某个时机打破retain环:

    self.queue = [[NSOperationQueue alloc] init]; 
    MyOperation *operation = [[MyOperation alloc] init]; 
    operation.completionBlock = ^{ 
        [self finishedOperation]; 
    }; 
    [self.queue addOperation:operation]; 

    乍一看这似乎是一个retain环:self retain了queue,queue retain了operation,而operation retain了completion block,而completion blockretain了self。不过,在这里,将operation添加到queue时,会使operation在某个时机被执行,然后从queue中remove掉(如果没有被执行,就会有大问题了)。一单queue移除了operation之后,retain环就被打破了。

    再来一个示例:这里实现了一个视频编码器的类,里面有一个名为encodeWithCompletionHandler:的方法。为了避免出现retain环,我们需要确保编码器这个对象能够在某个时机nil掉其对block的引用。其内部代码如下所示:

    @interface Encoder () 
    @property (nonatomic, copy) void (^completionHandler)(); 
    @end 
    
    @implementation Encoder 
    
    - (void)encodeWithCompletionHandler:(void (^)())handler 
    { 
        self.completionHandler = handler; 
        // do the asynchronous processing... 
    } 
    
    // This one will be called once the job is done 
    - (void)finishedEncoding 
    { 
        self.completionHandler(); 
        self.completionHandler = nil; // <- Don't forget this! 
    } 
    
    @end 

    在上面的代码中,一旦编码任务完成,就会调用complietion block,进而把引用nil掉。

    如果我们发送的消息属于一次性的(具体到某个方法的调用),由于这样可以打破潜在的retain环,那么使用block是非常不错的选择。另外,如果为了让代码可读性更强,更有连贯性,那最好是使用block了。根据这个思路,block经常可以用于completion handler、error handler等。

    Target-Action

    Target-Action主要被用于响应用户界面事件时所需要传递的消息中。iOS中的UIControl和Mac中的NSControl/NSCell都支持这种机制。Target-Action在消息的发送者和接收者之间建立了一个非常松散耦合。消息的接收者不知道发送者,甚至消息的发送者不需要预先知道消息的接收者。如果target是nil,action会在响应链(responder chain)中被传递,知道找到某个能够响应该aciton的对象。在iOS中,每个控件都能关联多个target-action。

    基于target-action消息传递的机制有一个局限就是发送的消息不能携带自定义的payload。在Mac的action方法中,接收者总是被放在第一个参数中。而在iOS中,可以选择性的将发送者和和触发action的事件作为参数。除此之外,没有别的办法可以对发送action消息内容做控制。

    做出正确的选择

    根据上面讨论的结果,这里我画了一个流程图,来帮助我们何时使用什么消息传递机制做出更好的决定。忠告:流程图中的建议并非最终的答案;可能还有别的选项依然能实现目的。只不过大多数情况下此图可以引导你做出正确的决定。

    这里写图片描述

    上图中,还有一些细节需要做更近一步的解释:

    上图中的有个盒子这样说到:sender is KVO compliant(发送者支持compliant)。这不仅以意味着当值发生改变时,发送者会发送KVO通知,并且观察者还需要知道发送者的生命周期。如果发送者被存储在一个weak属性中,那么发送者有可能被nil掉,进而引起观察者发生leak。

    另外底部的一个盒子说到:message is direct response to method call(消息直接在方法的调用代码中响应)。也就是说处理消息的代码跟方法的调用代码处于相同的地方。

    最后,在左下角,处于一个决策问题的判断状态:sender can guarantee to nil out reference to block?(发送者能够确保nil掉到block的引用吗?),这实际上涉及到之前我们讨论到基于block 的APIs已经潜在的retain环。使用block时,如果发送者不能保证在某个实际能够把对block的引用nil掉,那么将会遇到retain环的问题。

    Framework示例

    本节我们通过一些来自苹果Framework的示例,来看看在实际使用某种机制之前,苹果是处于何种原因做出选择的。

    KVO

    NSOperationQueue就是lion给了KVO来观察队列中operation状态属性的改变情况(isFinished, isExecuting, isCancelled)。当状态发生了改变,队列会受到一个KVO通知。为什么operationqueue要是用KVO呢?

    消息的接收者(operation queue)明确的知道发送者(opertation),以及通过retain来控制operation的生命周期。另外,在这种情况下,只需要单向的消息传递机制。当然,如果这样考虑:如果operation queue只关心operation值的改变情况,可能还不足以说服大家使用KVO。但是我们至少可以这样理解:什么机制可以对值的改变进行消息传递呢。

    这里写图片描述

    当然KVO也不是唯一的选择。我们可以这样设计:operation queue作为operation的delegate,operation会调用类似operationDidFinish: 或 operationDidBeginExecuting: 这样的方法,来将它的state传递给queue。这样一来,就不太方便了,因为operation需要将其state属性保存下来,一遍调用这些delegate方法。另外,由于queue不能主动获取state信息,所以queue也必须保存着所有operation的state。

    Notifications

    Core Data使用notification来传递事件(例如一个managed object context内部的改变——NSManagedObjectContextDidChangeNotification)。

    change notification是由managed object context发出的,所以我们不能确定消息的接收者一定知道发送者。如果消息并不是一个UI事件,而有可能多个接收者对该消息感兴趣,并且消息的传递属于单向(one-way communication channel),那么notification是最佳选择。

    这里写图片描述

    delegate

    Table view的delegate有多种功能,从accessory view的管理,到屏幕中cell显示的跟踪,都与delegate的功劳。例如,我们来看看 tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?

    正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。

    这里写图片描述

    如果消息传递中,不包含选中cell的index path,而是每当选中项改变时,我们主动去table view中获取到选中cell的相关信息,会怎样呢?其实这会非常的麻烦,因为这样一来,我们就必须记住当前选中项相关数据,以便获知被选中的cell。

    同理,虽然我们也可以通过观察table view中选中项的index paths属性值,当该值发生改变时,获得一个选中项改变的通知。不过,我们会遇到与上面同样的问题:不做任何记录的话,我们如何获知被选中项的相关信息。

    Block

    关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消息传递的机制。

    这里写图片描述

    还有其它可选的机制吗?当然有了,苹果自己的NSURLConnection就是最好的例子。NSURLConnection在block问世之前就已经存在了,所以它并没有利用block进行消息传递,而是使用delegation机制。当block出现之后,苹果在NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法(OSX 10.7 iOS 5),因此如果是简单的task,就不必在使用delegate了。

    在OS X 10.9 和 iOS 7中,苹果引入了一个非常modern的API:NSURLSession,其中使用block当做消息传递机制(NSURLSession仍然有一个delegate,不过是用于别的目的)。

    Target-Action

    Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。

    这里写图片描述

    如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。

    Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。

    小结

    首次接触这些机制,感觉它们都能用于两个对象间的消息传递。但是仔细琢磨一番,会发现它们各自有其需求和功能。

    文中给出的决策流程图可以为我们选择使用何种机制提供参考,不过图中给出的方案并不是最终答案,好多地方还需要亲自去实践。

    展开全文
  • Windows消息传递机制详解

    万次阅读 多人点赞 2015-03-19 13:44:34
    Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照...
  • 消息传递机制之Handler使用总结

    千次阅读 2016-11-09 13:49:06
    Handler消息处理机制是一个功能强大的数据传递机制,主要功能是用来把子线程的数据传递给主线程,让主线程进行UI操作. android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息...
  • Duilib的消息传递机制

    千次阅读 2016-07-31 11:17:00
    学会了怎么写XML文件,但是我还是不知道怎么实现各个控件之间消息传递。于是我对源代码好好研究了一下,发现duilib作为一个界面库有自己独立的封装的窗口类,也就是WindowsImplBase。 在这个类中,实现对windows...
  • 文章目录一、工厂机制factory二、组件配置机制uvm_config_db2.1.配置组件的DUT接口方式 一、工厂机制factory 二、组件配置机制uvm_config_db 2.1.配置组件的DUT接口方式 ...
  • Android消息通信之Activity间消息传递

    千次阅读 2019-03-09 17:13:17
    Android消息通信之Activity间消息传递 ... ... 一、消息通信机制 Android 开发之中我们常常需要应用到消息传递机制消息传递有多种方式。消息传递的作用不必多说,...
  • 在并发编程中,我们必须考虑的问题时如何在两个线程间进行通讯。...2、消息传递(actor 模型) 共享内存 共享内存这种方式比较常见,我们经常会设置一个共享变量。然后多个线程去操作同一个...
  • Android中Handler传递消息机制详解

    千次阅读 2016-04-17 17:44:35
    如果有多个线程并发操作UI,...例如我们在执行下载图片任务时,显然这是一个耗时任务,我们需要一个新线程来完成这个下载任务,当任务完成后我们需要将图片绘制到界面上,这时就需要Hanlder来发送一个消息给ImageView告
  • 尽管对象的表示在形式上与一般数据类型十分相似,但是它们之间存在一种本质区别:对象之间通过消息传递方式进行通信。 消息传递原是一种与通信有关的概念,OOP使得对象具有交互能力的主要模型就是消息传递模型。对象...
  • IOS消息传递机制

    千次阅读 2013-12-25 11:32:39
    注1:本文由破船译自Communication Patterns。 本文目录如下所示: ...本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么
  • postMessage跨域、跨iframe窗口消息传递

    千次阅读 多人点赞 2021-03-17 15:57:37
    window.postMessage()方法可以安全地实现Window对象之间的跨域通信。例如,在一个页面和它生成的弹出窗口之间,或者是页面和嵌入其中的iframe之间。 通常情况下,受浏览器“同源策略”的限制跨域问题一直是个问题,...
  • iOS 消息传递机制

    千次阅读 2013-12-18 08:05:30
    转载原地址:... 注1:本文由破船译自Communication Patterns。 ...可用的机制做出正确的选择Framework示例小结 ...每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的
  • 3、Akka传递消息

    千次阅读 2021-01-07 19:33:10
    1 消息传递 Akka 有 4 种核心的 Actor 消息模式: tell 、ask 、forward 和 pipe。 Ask:向 Actor 发送一条消息,返回一个 Future。当 Actor 返回响应时,会返回 Future。不会向消息发送者的邮箱返回任何消息。 ...
  • android消息处理机制

    千次阅读 2022-03-22 13:46:15
    android的消息处理 Looper类 Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,...
  • Handler消息(Message)传递机制详解

    千次阅读 2015-12-16 08:44:29
    本文从源码及结构角度,分析Handler消息(Message)传递机制,主要包括Handler、Looper、Message、MessageQueue等之间的关联。
  • CHROME插件开发——消息传递机制

    千次阅读 2015-09-24 16:30:11
    由于插件的js运行环境有区别,所以消息传递机制是一个重要内容。阅读了很多博文,大家已经说得很清楚了,直接转一篇@姬小光 的博文,总结的挺好。后面附一个自己写过的demo,基本就对消息传递能够熟悉了。 在开发...
  • Android消息循环机制: http://blog.csdn.net/crazy1235/article/details/51707527 Android Message.obtain() 和Handler.obtainMessage()的区别 --... Android的消息机制-Message机制...
  • 异步消息处理机制

    千次阅读 2021-12-01 14:55:11
    一、认识异步消息处理 所谓异步消息就是发送一个消息,不需要等待返回,随时可以再发送下一个消息。 异步消息处理线程启动后会进入一个无限的循环体之中,每...Message 是线程之间传递消息,它可以在内部携带少量信息,
  • Android Touch事件传递机制

    千次阅读 2015-09-13 14:25:36
    Touch事件传递机制,其实说起来还是比较复杂的,所涉及的内容和细节也都比较多。为了方便理解,本文将由浅入深的进行讲解。 首先要知道我们对于屏幕的所有操作,包括点击、放开、滑动,以及由这些基本操作组成的放大...
  • Android 消息通信之Activity间消息传递

    千次阅读 2018-03-02 12:21:40
    消息传递的作用不必多说,主要是在不同的组件之间传播消息或者共享某些状态等,以下是几种常用的消息传递机制: Intent 静态变量 全局变量 及Application Android系统剪切板 本地化存储方式 Andorid组件 EventBus ...
  • 对象之间的通信

    千次阅读 2013-05-25 19:14:58
    对象之间进行通信最基本的方式就是消息传递,在Cocoa中提供Notification Center机制来完成这一任务。其主要作用就是负责在任意两个对象之间进行通信。使用方法很简单,如下几个步骤即可:  假设A与B之间进行通信...
  • Android异步消息机制

    千次阅读 2021-11-16 15:12:16
    Message是在线程之间传递消息,可以在内部携带少量信息。 成员: what, arg1, arg2 Handler 用于发送和处理消息。 成员方法: sendMessage() handleMessage() MessageQueue 消息队列,用于存放所有通过Handler发送...
  • Handler消息传送机制总结

    千次阅读 多人点赞 2016-10-17 12:50:05
    2.Looper类用来管理特定线程内对象之间消息交换(MessageExchange)。 3.Message类用来保存数据。 (二)线程通信的过程 1.Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message...
  • netty3中Handler之间传递数据

    千次阅读 2017-08-29 20:00:12
    netty3中handler往下传递对象的方法是sendUpstream(event)方法服务端启动程序
  • 2、消息传递(actor 模型) 共享内存: 共享内存这种方式比较常见,我们经常会设置一个共享变量。然后多个线程去操作同一个共享变量。从而达到线程通讯的目的。例如,我们使用多个线程去执行页面抓取任务,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 354,711
精华内容 141,884
关键字:

对象之间消息传递的机制