Looper Thread in Linux

一、引言

在 Linux 系统的多线程编程中,Looper Thread 是一个非常重要的概念。它为处理异步事件和消息提供了一种高效的机制,使得系统能够在复杂的环境中保持良好的响应性和稳定性。理解 Looper Thread 的工作原理和应用场景对于深入掌握 Linux 系统编程至关重要。

二、Looper Thread 的基本概念

(一)线程与消息队列

线程是操作系统中独立运行的执行单元,而 Looper Thread 则是一种特殊的线程,它围绕着一个消息队列来工作。消息队列就像是一个存放任务或消息的容器,各种不同的消息可以被发送到这个队列中等待处理。Looper Thread 的主要职责就是不断地从这个消息队列中取出消息并进行处理。

(二)循环机制

Looper Thread 之所以被称为 “Looper”,是因为它具有一个循环机制。这个循环会不断地执行,使得线程能够持续地检查消息队列中是否有新的消息。当有消息时,它就会取出消息并调用相应的处理函数来处理消息。处理完消息后,它又会回到循环的开始处,继续检查消息队列,如此循环往复,直到线程被停止或销毁。

三、Looper Thread 的工作原理

(一)消息的发送与接收

当其他线程或组件需要向 Looper Thread 发送消息时,它们会通过特定的函数将消息放入消息队列中。Looper Thread 在循环过程中会不断地检查消息队列,当发现有新消息时,它会将消息取出。这个过程涉及到一些底层的同步机制,以确保消息的正确发送和接收,避免出现数据竞争或消息丢失的情况。

(二)消息的处理

取出消息后,Looper Thread 会根据消息的类型或内容来调用相应的处理函数。这些处理函数是事先注册好的,它们负责完成具体的任务,比如更新界面、处理网络请求、进行文件操作等。不同的消息可能对应不同的处理函数,这使得 Looper Thread 能够灵活地处理各种不同类型的任务。

(三)线程的阻塞与唤醒

在没有消息时,Looper Thread 通常会处于阻塞状态,以避免不必要的 CPU 资源消耗。当有新消息进入消息队列时,线程会被唤醒,继续执行循环并处理消息。这种阻塞与唤醒机制是通过操作系统提供的一些原语来实现的,比如信号量、互斥锁等。

四、Looper Thread 的应用场景

(一)图形界面编程

在 Linux 的图形界面应用中,Looper Thread 常用于处理用户输入事件,如鼠标点击、键盘按键等。当用户进行操作时,相应的事件会被发送到消息队列中,Looper Thread 会取出这些事件并更新界面,使得应用能够及时响应用户的操作。

(二)网络编程

在网络应用中,Looper Thread 可以用于处理网络数据包的接收和发送。当有网络数据到达时,相关的消息会被发送到消息队列中,Looper Thread 会取出消息并进行处理,比如解析数据包、更新网络连接状态等。

(三)事件驱动编程

许多 Linux 应用采用事件驱动的编程模型,Looper Thread 在这种模型中扮演着重要的角色。各种事件,如文件系统事件、定时器事件等,都可以通过消息的形式发送到 Looper Thread 的消息队列中,然后由 Looper Thread 进行处理。

五、Looper Thread 的实现方式

(一)基于操作系统的原生支持

Linux 操作系统提供了一些原生的机制来支持 Looper Thread 的实现,比如使用 pthread 库中的线程函数和相关的同步原语。通过创建一个线程,并在其中实现消息队列的管理和循环处理逻辑,可以构建一个简单的 Looper Thread。

(二)使用开源框架

许多开源框架,如 Android 的 Handler 机制,也提供了对 Looper Thread 的支持。这些框架通常封装了底层的实现细节,提供了更方便的接口供开发者使用。开发者可以通过继承相关的类或使用特定的 API 来创建和使用 Looper Thread。

六、Looper Thread 的性能优化

(一)减少消息队列的等待时间

为了提高 Looper Thread 的响应速度,需要尽量减少消息在消息队列中的等待时间。可以通过优化消息的处理逻辑,避免长时间的阻塞操作,以及合理调整消息的优先级等方式来实现。

(二)合理使用多线程

在某些情况下,单个 Looper Thread 可能无法满足系统的性能需求。这时可以考虑使用多个 Looper Thread 来处理不同类型的消息或任务,通过合理分配任务到不同的线程中,可以提高系统的整体性能。

(三)内存管理优化

由于 Looper Thread 可能会处理大量的消息和数据,因此需要注意内存的管理和优化。及时释放不再使用的内存空间,避免内存泄漏和碎片的产生,以保证系统的稳定性和性能。

七、Looper Thread 的注意事项

(一)线程安全

在使用 Looper Thread 时,必须确保线程安全。由于多个线程可能会访问消息队列和共享资源,因此需要使用适当的同步机制来保护这些资源,避免出现数据不一致或并发错误。

(二)消息处理的复杂性

随着应用的复杂性增加,消息处理的逻辑可能会变得非常复杂。需要合理设计消息处理函数,避免出现过于复杂的嵌套或递归逻辑,以免导致系统性能下降或出现难以调试的问题。

(三)资源的释放

当 Looper Thread 不再需要使用时,必须确保所有相关的资源都被正确释放,包括消息队列、线程资源、文件句柄等。否则可能会导致系统资源的浪费或出现内存泄漏等问题。

八、总结

Looper Thread 是 Linux 系统中一种非常重要的线程类型,它为处理异步事件和消息提供了一种高效的机制。通过围绕消息队列的循环处理,Looper Thread 能够及时响应各种事件和任务,使得系统能够在复杂的环境中保持良好的性能和稳定性。在实际应用中,需要根据具体的需求和场景来合理使用 Looper Thread,并注意性能优化和线程安全等问题。随着对 Looper Thread 的深入理解和掌握,开发者能够更好地利用这一机制来构建高效、稳定的 Linux 应用程序。

总之,可以想象一个工厂的生产线,“Looper Thread” 就像是这条生产线上的一个不知疲倦的工人。这个工人的工作就是不断地从一个 “任务篮子” 里取出任务,然后执行任务,接着再把完成任务后的结果放到另一个 “结果篮子” 里,之后又马上回到 “任务篮子” 去取下一个任务,如此循环往复,不停地工作。

在 Linux 系统中,“Looper Thread” 就类似这个工人,它会不断地从消息队列(相当于任务篮子)中取出消息(任务),然后处理消息,处理完后可能会把一些结果或者新的消息放到其他地方(相当于结果篮子),接着又继续从消息队列中取消息,一直这样循环运行,保证系统中各种事件和任务能够按照顺序被处理,就像生产线工人保证产品能按流程被生产出来一样。

1. Looper - 循环的核心 作用:负责不断地从MessageQueue中取出Message,并分发给对应的Handler进行处理。 准备Looper (prepare()): 一个线程要想拥有消息循环,必须首先调用Looper.prepare()。ThreadLocal保证了每个线程有且仅有一个独立的Looper实例。 // sThreadLocal是一个ThreadLocal<Looper>类型的静态变量 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { // 检查当前线程是否已经存在Looper,防止重复创建 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 创建新的Looper实例,并存入当前线程的ThreadLocal中 sThreadLocal.set(new Looper(quitAllowed)); } 这里看出,不能重复创建Looper,只能创建一个。创建Looper,并保存在ThreadLocal。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域 Looper的构造方法: java final MessageQueue mQueue; final Thread mThread; private Looper(boolean quitAllowed) { // 创建了与当前Looper绑定的MessageQueue mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 开启循环 (loop()): 这是Looper最核心的方法。调用Looper.loop()后,当前线程就进入了无限循环,不断地处理消息java public static void loop() { // 获取当前线程的Looper final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 获取Looper对应的消息队列 final MessageQueue queue = me.mQueue; ... // 无限循环开始 for (;;) { // 从消息队列中取消息,可能会阻塞 Message msg = queue.next(); if (msg == null) { // 没有消息表明消息队列正在退出,循环结束 return; } ... try { // 关键!将消息分发给它的target Handler进行处理 msg.target.dispatchMessage(msg); ... } catch (Exception exception) { ... } finally { ... } ... // 回收消息消息池,以便复用 msg.recycleUnchecked(); } } 获取当前线程Looper (myLooper()): public static @Nullable Looper myLooper() { // 直接从ThreadLocal中获取当前线程的Looper return sThreadLocal.get(); } 主线程的Looper: 应用启动时,系统已经自动为主线程调用了Looper.prepareMainLooper()并开启了loop(),所以我们无需再手动为主线程创建Looper。ActivityThread的main方法中包含了这些逻辑: java public static void main(String[] args) { ... Looper.prepareMainLooper(); // 内部也是调用了prepare(false),不允许退出 ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 2. MessageQueue - 消息仓库 作用:一个按时间优先级排序的消息队列,用于存放所有由Handler发送来的Message。其内部实现并非简单的Queue,而是基于单链表的数据结构。 入队 (enqueueMessage) Handler发送消息时,最终会调用到此方法。 java boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { ... msg.when = when; Message p = mMessages; // 链表头 boolean needWake; if (p == null || when == 0 || when < p.when) { // 新消息在链表头部,是最早需要执行的消息 msg.next = p; mMessages = msg; needWake = mBlocked; // 如果线程阻塞了,需要唤醒 } else { // 将新消息插入到链表中间 Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; prev.next = msg; } ... if (needWake) { nativeWake(mPtr); // 通过Native方法唤醒队列 } } return true; } 出队/取消息 (next) 这是Looper.loop()中调用的方法,是可能阻塞的关键。 Message next() { ... int pendingIdleHandlerCount = -1; // nextPollTimeoutMillis:下次轮询的超时时间,0表示立即返回,-1表示无限等待直到被唤醒 int nextPollTimeoutMillis = 0; for (;;) { ... // 1. 这是一个Native方法,用于阻塞线程。 // 当nextPollTimeoutMillis=-1时,线程阻塞直到被主动唤醒(enqueueMessage时调用nativeWake); // 当nextPollTimeoutMillis=0时,立即返回; // 当>0时,阻塞指定毫秒数后超时返回。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 寻找一个异步消息,同步屏障相关,后面会讲 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 消息执行时间未到,计算超时时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 找到了可执行的消息,从链表中移除并返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; ... return msg; } } else { // 没有消息,下次循环将无限等待,直到有消息入队被唤醒 nextPollTimeoutMillis = -1; } ... } ... } } 阻塞与唤醒:nativePollOnce和nativeWake是JNI方法,底层利用了Linux的epoll机制来实现高效的事件等待/唤醒模型,避免了CPU资源的浪费。 3. Message - 消息本体 作用:消息的载体,包含了需要处理的内容和相关信息。 成员变量: java public int what; // 消息标识,用于区分不同消息 public int arg1; // 轻量级的int数据存储 public int arg2; public Object obj; // 可存放任意对象 public long when; // 消息执行的绝对时间戳 ... Handler target; // 处理此消息的Handler,即发送它的那个Handler Runnable callback; // 一个Runnable对象,可以被执行 Message next; // 指向下一个Message,构成链表 消息池(回收与复用): 频繁地创建和销毁Message对象会引发GC,Android使用了一个消息池(链表结构)来复用Message,这是一个非常经典的享元模式应用。 java // 获取一个消息,优先从池中取 public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } // 回收消息 void recycleUnchecked() { // 清空消息的所有内容 flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; ... // 将清空后的消息放入池中 synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } 最佳实践:总是使用Message.obtain()或Handler.obtainMessage()来获取消息,而不是直接new Message()。 4. Handler - 发送与处理 作用:是给开发者使用的API,用于发送消息 (sendMessage) 和处理消息 (handleMessage)。 构造方法: java public Handler(@Nullable Callback callback, boolean async) { ... // 关键!获取当前线程的Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()"); } // 获取Looper消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 发送消息 (sendMessageAtTime): 无论调用sendMessageDelayed还是post(Runnable),最终都会走到这里。 java public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { ... return false; } // 将消息入队 return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 关键!将Message的target指向自己。 // 这样Looper从队列中取出消息后,就知道该调用哪个Handler的dispatchMessage了。 msg.target = this; ... // 调用MessageQueue的enqueueMessage方法 return queue.enqueueMessage(msg, uptimeMillis); } 分发消息 (dispatchMessage): 这是在Looper.loop()中调用的方法,是消息处理的枢纽。 java public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { // 情况1:消息自带Runnable回调,优先执行它 handleCallback(msg); } else { if (mCallback != null) { // 情况2:Handler构造时传入了Callback,尝试让它处理 if (mCallback.handleMessage(msg)) { return; } } // 情况3:最后交给子类重写的handleMessage方法处理 handleMessage(msg); } } 四、 高级话题:同步屏障与异步消息 上面的MessageQueue.next()方法中有一段寻找异步消息的代码,这涉及到一个高级机制:同步屏障 (Sync Barrier)。 什么是同步屏障? 它是一个特殊的、target为null的Message,被插入到消息队列中。它的作用是阻止所有的同步消息,只让异步消息得到执行。 如何设置屏障? MessageQueue.postSyncBarrier() 为何需要它? 用于处理高优先级、需要立即执行的任务,最典型的应用就是UI绘制和动画(ViewRootImpl.scheduleTraversals())。系统插入一个屏障,确保UI绘制的异步消息能优先于应用程序的同步消息执行,保证UI的流畅性。 异步消息: 在创建Handler时,传递async=true参数,或者通过Message.setAsynchronous(true)标记的消息。 工作原理: 当next()方法遇到target==null的屏障消息时,它会跳过后续所有的同步消息,只在队列中寻找异步消息来执行。直到调用removeSyncBarrier()移除屏障后,同步消息才能继续被处理。 完善这个段话,源码不要省略
09-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值