消息传递和同步屏障机制全面解析

本文详细解析了Android中的Handler消息机制,包括Handler、MessageQueue、Message和Looper的基本概念,以及消息的发送、入队、出队和分发流程。此外,还介绍了同步屏障如何确保高优任务如屏幕刷新的优先执行,以及IdleHandler在消息队列空闲时执行低优先级任务的机制。最后,探讨了Message对象池的复用策略,以减少内存消耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、消息机制原理

Handler消息机制老生常谈了,必备八股之一。但是每次看都有新收获,故好好总结一下Handler相关知识。

1.1 基本概念

1、Handler

用于发送和处理消息的类,有多种重载的构造方法,通过一系列sendXXXpostXXX方法来发送消息到消息队列,然后通过实现Handler.Callback接口或重写handleMessage方法处理消息

2、MessageQueue

消息队列,它是一个链表结构,用以存放handler发送的消息,实现了获取消息的方法next()和移除消息及消息处理回调的方法(removeXXX系列方法)

3、Message

消息,承载一些基本数据,消息队列存放对象。维护了一个消息对象池,可以复用消息,避免创建太多消息对象占用过多内存,导致APP卡顿。

消息分类:
在这里插入图片描述

4、Looper

消息机制的灵魂,用以不断调度消息对象并且分发给handler处理。Looper是同线程绑定的,不同线程的Looper不一样,通过ThreadLocal实现线程隔离。

1.2 消息机制主流程

1、发送消息

在这里插入图片描述

可以使用sendMessage(以及一系列 sendXXX的消息发送方法)和post方法发送即时同步消息,或通过sendXXXDelayed和postDelayed发送延迟同步消息。

如果是通过sendXXX方法发送即时或延时消息,最终都会辗转调用到sendMessageAtTime(@NonNull Message msg, long uptimeMillis)方法,然后调用enqueueMessage方法。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
   
   
        msg.target = this;// ① 设置处理该消息的handler对象
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
		// ② 设置消息类型,同步或异步
        if (mAsynchronous) {
   
   
            msg.setAsynchronous(true);
        }
  		// ③ 交由消息队列的入队方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }

该方法主要有3个作用,注释中的①②③分别说明了。

2、消息入队

消息入队最终是靠消息队列的恩queueMessage方法完成,其代码如下

boolean enqueueMessage(Message msg, long when) {
   
   
  		// ①
        if (msg.target == null) {
   
   
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
   
   
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
   
   
            if (mQuitting) {
   
   
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
			// ②
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
          	// ③
            if (p == null || when == 0 || when < p.when) {
   
   
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
   
   
                // ④
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
   
   
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
   
   
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
   
   
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

			// ⑤
            if (needWake) {
   
   
                nativeWake(mPtr);
            }
        }
        return true;
    }

注释中标明了5个注意点,👇一一说明下:

① 消息对象必须指定target,也就是处理消息的handler对象;而且message对象的flagFLAG_IN_USE。否则将抛出异常。

②设置消息对象标志FLAG_IN_USE和时间,创建唤醒字段,用于标记是否需要唤醒消息队列

③如果当前消息队列没有消息或要入队的消息when值小于对列头消息when值,则将新消息插入到链表头部。设置needWeak,它又由mBlocked变量决定,mBlocked的设置是在next()方法中,简单来说消息队列仅有延时消息或空队列时,mBlockedtrue

④不满足③的情况下,从消息链表头开始遍历,将新消息插入到第一个when值大于新消息when值的消息节点前方。

例如当前消息队里:100 - 30 -20 -10(数字表示消息的when值)

<think>好的,我现在需要帮助用户理解MFC消息机制中的内存屏障以及它能保证的内存可见性情况。用户之前问过关于跨线程传递指针的安全性,现在想更深入探讨消息机制本身如何保证内存可见性。 首先,我需要回顾一下MFC的消息机制。MFC是基于Windows消息队列的,消息发送如PostMessageSendMessage都是通过系统消息队列实现的。系统在处理这些消息时,内部应该会有同步机制,确保消息按顺序处理,这可能涉及到内存屏障。 内存屏障是什么?它是防止CPU指令重排序的机制,保证屏障前后的内存操作顺序。在跨线程通信中,如果没有适当的内存屏障,可能会出现可见性问题,比如一个线程修改了数据,另一个线程看不到修改后的值。 接下来,用户想知道MFC的消息机制中的内存屏障能保证哪些情况下的内存可见性。我需要分析不同消息传递方式,比如PostMessageSendMessage,以及不同数据传递方式,比如传值、传指针、传智能指针等,各自的内存可见性情况。 首先,对于PostMessageSendMessage,它们的区别在于是否阻塞发送线程。PostMessage是异步的,发送后立即返回,消息进入队列;SendMessage是同步的,直到消息处理完才返回。但两者的消息处理都是由接收线程完成的,系统在派发消息时会插入内存屏障,确保在此之前的内存修改对接收线程可见。 然后,针对不同的数据传递方式: 1. 传值:比如发送基本类型或结构体,这时候数据被复制到消息参数中,由于是通过系统消息队列传递副本,不存在可见性问题,因为接收方拿到的是独立的数据副本。 2. 传堆指针:比如用户之前的问题,发送new出来的对象指针。这里需要确保在发送后发送线程不再修改该指针指向的内存,并且接收线程正确释放。由于消息机制的内存屏障,接收线程在处理消息时能看到发送线程在发送前的所有内存修改,前提是发送线程在调用PostMessage之后不再修改数据。 3. 传智能指针:比如使用shared_ptr,需要注意线程安全性。如果智能指针的引用计数是原子操作的,那么传递指针本身是安全的,但指向的数据仍需保证在发送后不被修改。 接下来需要考虑可能的问题例外情况。比如,发送线程在PostMessage之后又修改了数据,这时候接收线程可能看到不一致的状态,因为内存屏障只保证到消息发送时的状态。此外,如果传递的是复杂对象,如含有虚函数表或内部指针的对象,需要确保这些结构在接收线程中正确解析,可能涉及更多的同步措施。 另外,需要提到哪些情况下内存屏障无法保证可见性。例如,发送线程在PostMessage之后继续修改数据,或者多个线程同时修改同一块内存而没有其他同步机制,这时候即使有消息机制的内存屏障,仍然可能出现可见性问题。 总结时,应该明确MFC消息机制的内存屏障保证的是在消息发送时之前的内存修改对接收线程可见,但发送之后的内存修改不保证。同时,传递指针需要严格的生命周期管理,避免访问已释放内存或数据竞争。 可能还需要对比其他同步机制,如临界区、事件等,说明消息机制在可见性上的优势限制。最后给出最佳实践,比如优先传值、使用智能指针管理所有权、避免发送后修改数据等。</think>以下是关于MFC消息机制中内存可见性问题的技术分析,采用分步式结构说明: ### 一、MFC消息机制的内存屏障本质 Windows消息队列通过**隐式内存屏障(Memory Barrier)**实现线程间通信,具体表现为: 1. **`PostMessage/SendMessage`调用点**:触发编译器内存屏障,保证调用前所有内存写入操作完成 2. **消息派发时**:系统内核确保消息处理线程看到发送线程在消息提交时的内存状态 3. **消息处理函数入口**:建立acquire语义,确保后续读取能看到发送线程的修改 ### 二、保障可见性的具体场景 #### ✅ 场景1:简单数据类型传值 ```cpp // 发送线程 int nValue = 42; pWnd->PostMessage(WM_UPDATE, nValue, 0); // 接收线程 afx_msg LRESULT OnUpdate(WPARAM wParam, LPARAM) { int received = (int)wParam; // 保证看到42 } ``` **保障机制**:值拷贝通过消息参数传递,无指针风险 #### ✅ 场景2:堆内存单次写入 ```cpp // 发送线程 std::string* pData = new std::string("init"); pData->assign("new value"); // 写入1 pWnd->PostMessage(WM_DATA, 0, (LPARAM)pData); // 接收线程 LRESULT OnData(WPARAM, LPARAM lParam) { std::string* p = (std::string*)lParam; cout << *p; // 保证看到"new value" delete p; } ``` **保障条件**: - 发送线程在PostMessage**后不再修改**pData内容 - 接收线程是**唯一**访问该内存的线程 #### ✅ 场景3:结构体完整初始化 ```cpp struct MyStruct { int a; bool b; double c; }; // 发送线程 MyStruct* p = new MyStruct{1, true, 3.14}; pWnd->PostMessage(WM_STRUCT, 0, (LPARAM)p); // 接收线程 LRESULT OnStruct(...) { MyStruct* p = reinterpret_cast<MyStruct*>(lParam); // 保证看到完整初始化的结构体 } ``` ### 三、不保障可见性的场景 #### ❌ 场景1:发送后继续修改 ```cpp // 危险代码! MyData* p = new MyData; p->value = 1; pWnd->PostMessage(WM_DATA, 0, (LPARAM)p); p->value = 2; // 接收线程可能看到1或2 ``` #### ❌ 场景2:无锁跨线程共享 ```cpp // 共享全局变量 volatile bool g_flag = false; // volatile不足以保证可见性 // 线程A g_flag = true; PostMessage(...); // 线程B(消息处理) if(g_flag) { ... } // 可能看不到true ``` **解决方法**:对共享变量使用`std::atomic`或临界区 #### ❌ 场景3:缓存行竞争 ```cpp struct alignas(64) Data { // 伪对齐 int a; int b; }; // 线程A修改Data::a后发消息 // 线程B读取Data::b时可能看到旧值 ``` **解决方法**:对关联数据使用`std::atomic_thread_fence` ### 四、强化可见性的编程模式 #### 模式1:写时复制(Copy-on-Write) ```cpp // 使用不可变数据 class ImmutableData { public: const int id; const std::string name; ImmutableData(int i, const std::string& s) : id(i), name(s) {} }; // 发送线程 auto p = new ImmutableData(123, "safe"); PostMessage(..., (LPARAM)p); ``` #### 模式2:带版本标记的指针 ```cpp struct VersionedPtr { std::atomic<uint32_t> version; void* pData; }; // 发送端 verPtr.version.store(rev++, std::memory_order_release); PostMessage(..., &verPtr); // 接收端 uint32_t v = verPtr.version.load(std::memory_order_acquire); ``` #### 模式3:内存域隔离 ```cpp // 使用专用堆 HANDLE hHeap = HeapCreate(0, 0, 0); void* p = HeapAlloc(hHeap, 0, size); // 发送指针... // 接收端处理完成后 HeapFree(hHeap, 0, p); ``` ### 五、调试检测方法 1. **Application Verifier**:检测跨线程内存访问违规 2. **WinDbg命令**: ``` !heap -p -a <address> // 验证堆块归属 !address <address> // 检查内存属性 ``` 3. **代码注入检测**: ```cpp #ifdef _DEBUG #define VALIDATE_PTR(p) \ if (IsBadReadPtr(p, sizeof(*p))) \ DebugBreak(); #endif ``` ### 六、性能优化建议 1. **消息批处理**:对高频更新使用`WM_TIMER`合并处理 2. **无锁环形缓冲区**:适用于实时数据流 ```cpp template<typename T, size_t N> class MessageBuffer { std::atomic<size_t> writeIdx; T data[N]; public: bool Push(const T& item) { /* CAS操作 */ } bool Pop(T& out) { /* 原子读取 */ } }; ``` 3. **内存池优化**:对频繁传递的同类型对象使用对象池 ### 附:MFC消息处理流程内存屏障关系 ``` 发送线程 │ ├─ 写内存操作 │ ├─ 普通变量赋值 │ └─ 堆内存初始化 │ ▼ PostMessage/SendMessage │ # 隐式编译器屏障 ▼ Windows内核消息队列 │ # 系统级内存屏障 ▼ 接收线程消息泵 │ # acquire语义屏障消息处理函数 │ └─ 读取操作看到发送线程的写入 ``` 通过合理利用消息机制的内存可见性特征,可以构建出高效且线程安全的MFC应用程序,但必须严格遵循"写入后不修改"的原则,对复杂场景建议结合更精细的同步机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值