操作系统底层运行原理 —— 基于线程安全的消息机制

前言

学过Android应用开发的大概都知道Handler这个东东,这也是面试中老生常谈的问题。其实不仅仅是Android,iOS以及PC的操作系统,底层也离不开消息机制。这个属于生产消费者问题。

什么是生产者消费者模式

生产者消费者模式(Producer-Consumer Pattern)是一种多线程协作的设计模式,其中有两种不同类型的线程:生产者和消费者。这两种线程通过共享的缓冲区(队列、缓冲池等)进行通信,以协调任务的执行。

在生产者消费者模式中,生产者负责生成一些数据或执行一些任务,并将其放置到共享的缓冲区中。而消费者则负责从缓冲区中获取数据或任务,并进行处理。这样,生产者和消费者可以在不同的时间段内独立地工作,通过共享的缓冲区进行数据交换,实现了解耦和协作。

典型的生产者消费者模式中包含以下元素:

  1. 生产者(Producer): 生成数据或执行任务,并将其放入共享缓冲区。
  2. 消费者(Consumer): 从共享缓冲区获取数据或任务,并进行相应的处理。
  3. 共享缓冲区: 用于存储生产者生成的数据或任务,以供消费者获取。
  4. 同步机制: 用于确保生产者和消费者之间的协调和同步,防止竞争条件(Race Condition)和死锁(Deadlock)等问题。

生产者消费者模式的一个典型场景是任务队列。生产者将任务放入队列,而消费者从队列中取出任务并执行。这种模式可以有效地利用多线程的优势,提高系统的效率和性能。

使用C++手写一个简易的消息处理机制

效果演示

完整代码

cpp 复制代码
//
//  main.cpp
//  ProducerCustomer
//
//  Created by dora on 2023/12/23.
//

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

#define EVENT_TYPE_1 0
#define EVENT_TYPE_2 1
#define EVENT_TYPE_3 2

std::queue<int> buffer;             // 共享缓冲区
std::mutex mtx;                     // 互斥锁
std::condition_variable condition;         // 条件变量
const int bufferSize = 5;            // 消息的缓冲区大小

// 生产者线程函数
void producer() {
    while (true) {
        {
            // 生产者休眠40毫秒,太快会导致消费者线程处理不过来,如造成UI卡顿掉帧
            std::chrono::milliseconds sleepDuration(40);
            std::this_thread::sleep_for(sleepDuration);
            std::unique_lock<std::mutex> lock(mtx);
            // 等待条件满足:缓冲区不满
            condition.wait(lock, [] { return buffer.size() < bufferSize; });
            // 生产数据并放入缓冲区
            buffer.push(EVENT_TYPE_1);
            std::cout << "Produced: " << EVENT_TYPE_1 << " Event Size:("<< buffer.size() <<")\n";
        }
        // 通知消费者缓冲区非空
        condition.notify_all();
    }
}

void producer2() {
    while (true) {
        {
            // 生产者休眠40毫秒,太快会导致消费者线程处理不过来,如造成UI卡顿掉帧
            std::chrono::milliseconds sleepDuration(40);
            std::this_thread::sleep_for(sleepDuration);
            std::unique_lock<std::mutex> lock(mtx);
            // 等待条件满足:缓冲区不满
            condition.wait(lock, [] { return buffer.size() < bufferSize; });
            // 生产数据并放入缓冲区
            buffer.push(EVENT_TYPE_2);
            std::cout << "Produced: " << EVENT_TYPE_2 << " Event Size:("<< buffer.size() <<")\n";
        }
        // 通知消费者缓冲区非空
        condition.notify_all();
    }
}

void producer3() {
    while (true) {
        {
            // 生产者休眠40毫秒,太快会导致消费者线程处理不过来,如造成UI卡顿掉帧
            std::chrono::milliseconds sleepDuration(40);
            std::this_thread::sleep_for(sleepDuration);
            std::unique_lock<std::mutex> lock(mtx);
            // 等待条件满足:缓冲区不满
            condition.wait(lock, [] { return buffer.size() < bufferSize; });
            // 生产数据并放入缓冲区
            buffer.push(EVENT_TYPE_3);
            std::cout << "Produced: " << EVENT_TYPE_3 << " Event Size:("<< buffer.size() <<")\n";
        }
        // 通知消费者缓冲区非空
        condition.notify_all();
    }
}

// 消费者线程函数
void consumer() {
    while (true) {
        {
            std::unique_lock<std::mutex> lock(mtx);
            // 等待条件满足:缓冲区非空
            condition.wait(lock, [] { return !buffer.empty(); });
            // 消费数据
            int data = buffer.front();
            buffer.pop();
            std::cout << "Consumed: " << data << " Event Size:("<< buffer.size() <<")\n";
        }
        // 通知生产者缓冲区不满
        condition.notify_all();
    }
}

int main() {
    // 创建生产者和消费者线程
    std::thread producerThread(producer);
    std::thread producerThread2(producer2);
    std::thread producerThread3(producer3);
    std::thread consumerThread(consumer);

    // 等待线程结束
    producerThread.join();
    producerThread2.join();
    producerThread3.join();
    consumerThread.join();
    system("pause");
    return 0;
}

代码解析

这里一共使用了三个生产者线程,一个消费者线程。消费者线程通常就是我们Android中的UI线程或者叫主线程,Handler mH所对应的就是主线程,因为它对应的就是主线程的Looper。由于这只是一个简单的模拟,实际上还会使用到如thread_local_posix.h,Linux使用的是POSIX标准的线程。生产者模拟的就是消息的发送者,消费者模拟的就是消息处理器Handler,所有消息发送到主线程的Handler统一处理,比如发送广播、启动服务、打开一个Activity等。

mutex: 互斥变量

unique_lock: 锁,作用同 java.util.concurrent.locks.Lock

condition_variable: 条件变量,作用同 java.util.concurrent.Condition

当然Java中我们通常使用的是ReentrantLock。std::unique_lock的析构函数会自动释放锁,从而避免了手动调用 unlock。在性能上,ReentrantLock支持可重入性,需要维护更多的状态信息,所以无论是算法复杂度还是语言上性能都是不如std::unique_lock的。queue的front函数只查看队列最前面的元素,而不移除掉,而pop函数则是移除掉队列最前面的元素。[] {}是一个Lambda表达式,C++11开始支持的特性。condition_variable的notify_allnotify_one都是用于通知其他等待锁被释放的线程可以开始执行,只不过notify_all会通知所有等待在条件变量上的线程,让它们都尝试重新获取锁并继续执行。这样,所有等待的线程都有机会获得锁。而notify_one只会通知等待在条件变量上的一个线程,具体是哪一个线程取决于实现和调度器的策略。这样,只有一个线程会获得锁,其他线程仍然保持等待状态。

总结

线程安全是多线程开发的一个必不可少的技能,让多线程可以更好的协作执行任务。相当于在马路上制定了交通规则,即使很多车,也不会相撞。当然在现实生活中,会有意外发生,在程序的世界里,除非程序异常退出,否则都是按规则执行任务的。本文主要是让大家熟悉一下C/C++底层语言,为Android NDK开发打基础。

相关推荐
P.H. Infinity8 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天12 分钟前
java的threadlocal为何内存泄漏
java
霁月风16 分钟前
设计模式——适配器模式
c++·适配器模式
caridle24 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^29 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋333 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花37 分钟前
【JAVA基础】Java集合基础
java·开发语言·windows
jrrz082838 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
小松学前端40 分钟前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan1 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava