逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战

概述

在并发编程中,我们经常会遇到多个线程或协程访问共享资源的情况。为了保护这些资源不被同时修改,我们会用到"锁"的概念。

Go中提供了读写锁:sync.RWMutex。
sync.RWMutex是Go语言提供的一个基础同步原语,它是Reader/Writer Mutual Exclusion Lock的缩写,通常被称为"读写锁"。
读写锁允许多个读锁同时拥有者,但在任何时间点只允许一个写锁拥有者,或者没有锁拥有者。

这让读多写少的场景获得了更高的并发性能。

应用场景

  1. 典型应用场景就是读多写少
  2. 一写多读

提供的方法

sync.RWMutex提供了以下方法:

type RWMutex
// 获取写锁,有读锁或者写锁被其他goroutine使用则阻塞等待
func (rw *RWMutex) Lock()
// 尝试获取写锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryLock() bool
// 释放写锁
func (rw *RWMutex) Unlock()
// 获取读锁,
func (rw *RWMutex) RLock()
// 尝试获取读锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryRLock() bool
// 释放读锁
func (rw *RWMutex) RUnlock()

// 返回Locker
func (rw *RWMutex) RLocker() Locker

COPY

注意

使用RWMutex的时候,一旦调用了Lock方法,就不能再把该锁复制到其他地方使用,否则可能会出现各种问题。这是由于锁的状态(被哪个协程持有,是否已经被锁定等)是存储在RWMutex的结构体中,如果复制了RWMutex,那么复制后的RWMutex就会有一个全新的状态,锁的行为就会变得不可预测。
RWMutex和Mutex一样,一旦有了Lock调用就不能到处copy了,否则出现各种问题。

源码实现

RWMutex结构体

让我们一起深入Go的源码,看看RWMutex是如何实现的。
RWMutex 的结构体主要包括五个主要的字段,这些字段描述了锁的当前状态和持有者信息:

type RWMutex struct {
   // Mutex,互斥锁。写者互斥锁,所有的写者加锁都调用w.Lock或者w.TryLock
    w           Mutex   

    // 写者信号量。当最后一个读者释放了锁,会触发一个信号通知writerSem
    writerSem   uint32  

    // 读者信号量。当写者释放了锁,会触发一个信号通知readerSem
    readerSem   uint32      

    // readerCount 记录当前持有读锁的协程数量。如果为负数,表示有写者在等待所有读者释放锁。如果为0,表示没有任何协程持有锁
    readerCount atomic.Int32 

   // readerWait 记录写者需要等待的读者数量。当一个写者获取了锁之后,readerWait会设置为当前readerCount的值。当读者释放锁时,readerWait会递减1
    readerWait  atomic.Int32 
}

COPY

读者加锁RLock()

加读锁时非常简单,就是将结构体中的readerCount加1,如果+1后为负数表示有写者等待则等待写者执行完成。

实现代码

func (rw *RWMutex) RLock() {
    // 读者数量+1
    if rw.readerCount.Add(1) < 0 {
        // 加1以后如果readerCount是负数表示有写者持有了互斥锁
        // 读者等待信号量释放
        // 此时读锁已经加上了,等待写者释放信号量就可以了
        runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
    }
}

COPY

读者RTryLock()

这个函数是RWMutex中的TryRLock方法,它试图以非阻塞的方式获取读锁。让我们一步一步地看它是如何工作的。
先看图:

a771641e0c3056616025f39cfa7076b2.png

实现代码


func (rw *RWMutex) TryRLock() bool {
    for {
        // 查看当前读者数量
        c := rw.readerCount.Load()
        if c < 0 {
            // 小于0表示有写者已经Penging,加锁失败
            return false
        }
        // 读者数量+1,加读锁成功
        if rw.readerCount.CompareAndSwap(c, c+1) {
            return true
        }
    }
}

COPY

读者释放读锁RUnlock()

RUnlock方法用于释放读锁。 当一个读者完成读操作并想要释放锁时,就可以调用这个方法。

1af56f016db14721529ee7dd77dfb6bb.png

实现代码


func (rw *RWMutex) RUnlock() {
    // 释放锁就是-1,
    // 如果readerCount小于0表示有写者Pending
    // 进入rUnlockSlow
    if r := rw.readerCount.Add(-1); r < 0 {
        rw.rUnlockSlow(r)
    }
}

func (rw *RWMutex) rUnlockSlow(r int32) {
    // 边界问题处理
    // r+1 ==0 表示没有读者加锁,却调用了释放读锁
    // r+1 == -rwmutexMaxReaders表示没有读者加锁,有写者持有互斥锁却释放读锁
    if r+1 == 0 || r+1 == -rwmutexMaxReaders {
        race.Enable()
        fatal("sync: RUnlock of unlocked RWMutex")
    }

    // 这表示这是最后一个读者了,最后一个读者要发送信号量通知写者不用等了
    if rw.readerWait.Add(-1) == 0 {
        // The last reader unblocks the writer.
        runtime_Semrelease(&rw.writerSem, false, 1)
    }
}

COPY

写者加锁Lock()

实现代码


const rwmutexMaxReaders = 1 << 30

func (rw *RWMutex) Lock() {
    // 先持有互斥锁,已经有其他写者持有了互斥锁则等待
    rw.w.Lock()

    // rw.readerCount.Add(-rwmutexMaxReaders)这个表示先将readerCount设置为负数表示有写者在等待
    // 再+rwmutexMaxReaders是为了求出当前reader的数量
    r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders

    // 将当前reader的数量加到readerWait表示要等待的读者完成的个数
    if r != 0 && rw.readerWait.Add(r) != 0 {
        // 阻塞等待万有的读者完成释放信号量了
        runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
    }
}

COPY

写者加锁TryLock()

实现代码


func (rw *RWMutex) TryLock() bool {
    // 调用互斥锁的TryLock,互斥锁TryLock返回false这儿也直接返回false
    if !rw.w.TryLock() {
        return false
    }

    // 加锁成功后
    // 如果当前还有写者,CompareAndSwap就返回失败
    if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) {
        // 返回失败就释放互斥锁
        rw.w.Unlock()
        // 加锁失败
        return false
    }
    // 加锁成功
    return true
}

COPY

写者解锁Unlock()

实现代码


func (rw *RWMutex) Unlock() {
    // 这里是对Lock readerCount的逆向操作
    // 在Lock的时候对readerCount减去了rwmutexMaxReaders,这次加回来;这样就还原了readerCount,即使在Lock之后依然有读者加锁
    r := rw.readerCount.Add(rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
        race.Enable()
        fatal("sync: Unlock of unlocked RWMutex")
    }

    // 然后循环看当前有多少读者正在等待信号,就释放多少次心血号
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false, 0)
    }
    // Allow other writers to proceed.
    rw.w.Unlock()
}

COPY

测试


package mutex_test

import (
    "sync"
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
)

// 测试读写互斥锁在正常读锁定和解锁情况下的成功执行
func TestRWMutex_ShouldSuccess_WhenNormalReaderLockAndUnLock(t *testing.T) {
    // 初始化一个读写互斥锁
    rwmutex := sync.RWMutex{}
    // 获取读锁
    rwmutex.RLock()
    // 设置成功标志为true,使用defer确保在函数结束时释放读锁
    isSuccess := true
    defer rwmutex.RUnlock()
    // 记录日志表示测试成功
    t.Log("success")
    // 断言成功标志为true
    assert.True(t, isSuccess)
}

// 测试RWMutex的写锁功能是否正常
func TestRWMutex_ShouldSuccess_WhenNormalWriterLockAndUnLock(t *testing.T) {
    rwmutex := sync.RWMutex{} // 创建一个sync.RWMutex类型的变量
    rwmutex.Lock()            // 获取写锁
    isSuccess := true         // 标记为成功状态
    defer rwmutex.Unlock()    // 确保在函数退出时释放锁,避免死锁
    t.Log("success")          // 记录测试日志
    assert.True(t, isSuccess) // 断言isSuccess为true,验证操作成功
}

// 函数测试了在正常情况下,
// 读写锁(RWMutex)的读锁(RLock)和写锁(Lock)的加锁与解锁操作是否成功。
func TestRWMutex_ShouldSuccess_WhenNormalReaderWriterLockAndUnLock(t *testing.T) {
    // 初始化一个读写锁
    rwmutex := sync.RWMutex{}
    // 尝试获取读锁并立即释放
    rwmutex.RLock()
    rwmutex.RUnlock()
    // 尝试获取写锁并立即释放
    rwmutex.Lock()
    rwmutex.Unlock()
    // 标记测试为成功
    isSuccess := true
    // 记录测试成功日志
    t.Log("success")
    // 断言测试结果为真
    assert.True(t, isSuccess)
}

// 测试读写锁在多协程情况下的读写互斥
func TestRWMutex_ShouldSuccess_WhenReaderAndWriterInDifferentRoutine(t *testing.T) {
    // 初始化一个读写锁和等待组,用于协调不同协程的操作。
    rwmutex := sync.RWMutex{}
    wg := sync.WaitGroup{}
    wg.Add(2) // 预期有两个协程完成操作

    // 启动一个协程作为读锁持有者
    go func() {
        rwmutex.RLock()   // 获取读锁
        println("reader") // 打印读操作标识
        rwmutex.RUnlock() // 释放读锁
        wg.Done()         // 表示读操作完成
    }()

    // 启动另一个协程作为写锁持有者
    go func() {
        rwmutex.Lock()    // 获取写锁
        println("writer") // 打印写操作标识
        rwmutex.Unlock()  // 释放写锁
        wg.Done()         // 表示写操作完成
    }()

    wg.Wait() // 等待所有协程完成操作
    isSuccess := true
    t.Log("success")          // 记录测试成功
    assert.True(t, isSuccess) // 断言测试结果为真
}

// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBlockWriter_WhenMultipleReader(t *testing.T) {
    rwmutex := sync.RWMutex{}
    ch := make(chan bool)
    wg := sync.WaitGroup{}
    wg.Add(2)
    for i := 0; i < 2; i++ {
        go func(i int) {
            wg.Done()
            rwmutex.RLock()
            println("reader Locked", i)
            time.Sleep(10 * time.Second)
            rwmutex.RUnlock()
            println("reader UnLocked", i)
        }(i)
    }

    go func() {
        wg.Wait()
        println("writer try to accquire wlock")
        rwmutex.Lock()
        println("writer has accquired wlock")
        defer rwmutex.Unlock()
        ch <- true
    }()

    <-ch
    isSuccess := true
    t.Log("success")
    assert.True(t, isSuccess)
}

// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBlockReaders_WhenWriterIsPresent(t *testing.T) {
    rwmutex := sync.RWMutex{}
    wg := sync.WaitGroup{}
    wg.Add(1)

    go func() {
        println("writer try to accquire wlock")
        rwmutex.Lock()
        println("writer has accquired wlock")
        wg.Done()
        time.Sleep(10 * time.Second)
        defer rwmutex.Unlock()
        println("writer has released wlock")
    }()

    wg.Wait()
    wg.Add(2)
    for i := 0; i < 2; i++ {
        go func(i int) {
            println("reader try to lock", i)
            rwmutex.RLock()
            println("reader Locked", i)
            rwmutex.RUnlock()
            println("reader UnLocked", i)
            wg.Done()
        }(i)
    }

    wg.Wait()
    isSuccess := true
    t.Log("success")
    assert.True(t, isSuccess)
}

// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBlockConcurrentWriters(t *testing.T) {
    rwmutex := sync.RWMutex{}
    var blockedWriter bool
    ch := make(chan bool)
    wg := sync.WaitGroup{}

    wg.Add(1)
    go func() {
        wg.Done()
        println("Writer 1 try to accquire wlock")
        rwmutex.Lock()
        println("Writer 1 has accquired wlock")
        defer rwmutex.Unlock()
        time.Sleep(15 * time.Second)
    }()

    go func() {
        wg.Wait()
        println("Writer 2 try to accquire wlock")
        rwmutex.Lock()
        println("Writer 2 has accquired wlock")
        ch <- true
        defer rwmutex.Unlock()
    }()

    select {
    case <-ch:
        blockedWriter = false
    case <-time.After(20 * time.Second):
        blockedWriter = true
    }
    assert.True(t, blockedWriter)
}

// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldLockSuccess_WhenTryingToReadLockTwice(t *testing.T) {
    rwmutex := sync.RWMutex{}
    writerWaitGroup := sync.WaitGroup{}
    writerWaitGroup.Add(1)

    go func() {
        rwmutex.RLock()
        println("readlock locked once")
        rwmutex.RLock()
        println("readlock locked twice")
        rwmutex.RUnlock()
        rwmutex.RUnlock()
        defer writerWaitGroup.Done()
    }()

    writerWaitGroup.Wait()
    isSuccess := true

    assert.True(t, isSuccess)
}

// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenTryingToWriteLockTwice(t *testing.T) {
    rwmutex := sync.RWMutex{}
    ch := make(chan bool)
    go func() {
        rwmutex.Lock()
        println("writelock locked once")
        rwmutex.Lock()
        println("writelock locked twice")
        rwmutex.Unlock()
        rwmutex.Unlock()
        ch <- true
    }()

    isBlocked := false

    select {
    case <-ch:
        println("should not execute this block")
        assert.False(t, isBlocked)
    case <-time.After(10 * time.Second):
        isSuccess := true
        println("executed timeout block")
        assert.True(t, isSuccess)
    }

}

// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenAccquireWriteLockThenReadLock(t *testing.T) {
    rwmutex := sync.RWMutex{}
    ch := make(chan bool)
    go func() {
        rwmutex.Lock()
        println("writelock locked once")
        rwmutex.RLock()
        println("readlock locked twice")
        rwmutex.RUnlock()
        rwmutex.Unlock()
        ch <- true
    }()
    isBlocked := false

    select {
    case <-ch:
        println("should not execute this block")
        assert.False(t, isBlocked)
    case <-time.After(10 * time.Second):
        isSuccess := true
        println("executed timeout block")
        assert.True(t, isSuccess)
    }

}

// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenAccquireReadLockThenWriteLock(t *testing.T) {
    rwmutex := sync.RWMutex{}
    ch := make(chan bool)
    go func() {
        rwmutex.RLock()
        println("readlock locked once")
        rwmutex.Lock()
        println("writelock locked twice")
        rwmutex.Unlock()
        rwmutex.RUnlock()
        ch <- true
    }()
    isBlocked := false

    select {
    case <-ch:
        println("should not execute this block")
        assert.False(t, isBlocked)
    case <-time.After(10 * time.Second):
        isSuccess := true
        println("executed timeout block")
        assert.True(t, isSuccess)
    }

}

// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldDeadlockOrBlocked_WhenLockOneGoroutineAccquiredLockAndAnotherGoroutineAccquireLockAgain(t *testing.T) {
    var rwmutex1, rwmutex2 sync.RWMutex
    wg := sync.WaitGroup{}
    wg1 := sync.WaitGroup{}
    ch := make(chan bool)

    wg.Add(1)
    wg1.Add(1)
    go func() {
        rwmutex1.Lock()
        println("rwmutex1 locked")
        wg.Done()
        wg1.Wait()
        println("rwmutex2 try to accquire lock")
        rwmutex2.Lock()
    }()
    go func() {
        wg.Wait()
        rwmutex2.Lock()
        println("rwmutex2 locked")
        wg1.Done()
        println("rwmutex1 try to accquire lock")
        rwmutex1.Lock()
        ch <- true
    }()
    isBlocked := false

    select {
    case <-ch:
        println("should not execute this block")
        assert.False(t, isBlocked)
    case <-time.After(10 * time.Second):
        isSuccess := true
        println("executed timeout block")
        assert.True(t, isSuccess)
    }

}

参考

 逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战 – 小厂程序员

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/532907.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3D Matching:实现halcon中的find_surface_model

halcon中的三维匹配大致分为两类&#xff0c;一类是基于形状的(Shape-Based)&#xff0c;一类是基于表面的(Surface-Based)。基于形状的匹配可用于单个2D图像中定位复杂的3D物体&#xff0c;3D物体模型必须是CAD模型&#xff0c;且几何边缘清晰可见&#xff0c;使用的相机也要预…

com.intellij.diagnostic.PluginException 问题

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 未经允许不得转载 目录 一、导读二、 推荐阅读 一、导读 遇到…

淘宝批量采集商品详情数据(属性丨详情图丨sku丨价格等)

淘宝批量采集商品详情数据&#xff08;包括属性、详情图、SKU、价格等&#xff09;可以通过以下几种方式实现&#xff1a; 使用淘宝数据抓取工具&#xff1a;这类工具&#xff0c;如某鱼等&#xff0c;能够自动化采集淘宝商品数据&#xff0c;并将其转换成CSV、Excel等格式&am…

潍微科技-水务信息管理平台 ChangePwd SQL注入漏洞复现

0x01 产品简介 水务信息管理平台主要帮助水务企业实现水质状态监测、管网运行监控、水厂安全保障、用水实时监控以及排放有效监管,确保居民安全稳定用水、环境有效保护,全面提升水务管理效率。由山东潍微科技股份有限公司研发,近年来,公司全力拓展提升水务、水利信息化业务…

[react] 受控组件和非受控组件

什么是受控? 就是比如一个文本框,你可以随便输入就是受控,他收到状态的影响 <div className"App" >受控<input value{name}></input><br />非受控<input defaultValue{name}></input></div > 你想强行改?浏览器报错!…

Substance 3D2024版 下载地址及安装教程

Substance 3D是Adobe公司推出的一套全面的3D设计和创作工具集合&#xff0c;用于创建高质量的3D资产、纹理和材质。 Substance 3D包括多个功能强大的软件和服务&#xff0c;如Substance 3D Painter、Substance 3D Designer和Substance 3D Sampler等。这些工具提供了广泛的功能…

Kubesphere 在 devops 部署项目的时候下载 maven 依赖卡住

Kubesphere 在 devops 部署项目的时候下载 maven 依赖卡住 我下载 下面这段 maven 依赖一直卡住&#xff1a; <build><plugins><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>…

HarmonyOS实战开发-如何实现电话服务中发送短信的功能。

介绍 本示例使用ohos.telephony.sms 接口展示了电话服务中发送短信的功能。 效果预览 使用说明&#xff1a; 首页点击创建联系人&#xff0c;弹框输入联系人姓名和电话&#xff0c;点击确定按钮后&#xff0c;联系人列表中添加该联系人;点击管理&#xff0c;该按钮变成取消&…

SpringMVC--核心概念 / @RequestMapping注解

目录 1. 准备工作 1.1. 创建SpringMVC-demo02 子模块 1.2. 添加相关依赖 1.3. 设置 Maven 打包方式 1.4. 配置 web.xml 文件 1.4.1. 创建 web.xml 文件 1.4.2. 默认配置方式 1.4.3. 扩展配置方式 1.4.4. 注意点 1.5. 配置 Spring 文件 1.5.1. Thymeleaf视图解析器 …

【opencv】示例-facial_features.cpp 使用Haarcascade分类器检测面部特征点

// 包含OpenCV库中有关对象检测的头文件 #include "opencv2/objdetect.hpp" // 包含OpenCV库中有关高层GUI函数的头文件 #include "opencv2/highgui.hpp" // 包含OpenCV库中有关图片处理的头文件 #include "opencv2/imgproc.hpp"// 包含输入输出…

Java集合List

List特有方法 经典多态写法 // 经典的多态写法 List<String> list new ArrayList<>();常用API&#xff1a;增删改查 // 添加元素 list.add("Java"); // 添加元素到指定位置 list.add(0, "Python");// 获取元素 String s list.get(0);// 修改…

手机银行客户端框架之TMF框架介绍

腾讯移动开发平台&#xff08;Tencent Mobile Framework&#xff09;整合了腾讯在移动产品中开发、测试、发布和运营的技术能力&#xff0c;为企业提供一站式、覆盖全生命周期的移动端技术平台。核心服务包括移动客户端开发组件、H5容器、灰度发布、热更新、离线包、网关服务、…

【matlab】如何解决打开缓慢问题(如何让matlab在十几秒内打开)

【matlab】如何解决打开缓慢问题&#xff08;如何让matlab在十几秒内打开&#xff09; 找到我们解压缩时Crack中的license_standalone.lic文件&#xff0c;将其拷贝 在安装matlab的路径下新建一个文件&#xff0c;粘贴上面的license_standalone.lic文件 在桌面鼠标移动到matl…

面向对象设计原则实验之“迪米特法则”

每一个软件单位对其它单位都只有最少的知识&#xff0c;而且局限于那些与本单位密切相关的软件单位。 某软件公司所开发 CRM 系统包含很多业务操作窗口。在这些窗口中某些界面控件之间存在复杂的交互关系&#xff0c;一个控件事件的触发将导致多个其他界面控件产生响应。例如&…

ChatGPT在地学,自然科学等了领域应用教程

原文链接&#xff1a;ChatGPT在地学&#xff0c;自然科学等了领域应用教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247600722&idx2&sn291ea8c935b1d9b1459170baa9057053&chksmfa820bb5cdf582a39086e5ee9596ab020784fa78ac7dc49ced4969e28817c3f0…

在Debian 12系统上安装Docker

Docker 在 Debian 12 上的安装 安装验证测试更多信息 引言 在现代的开发环境中&#xff0c;容器技术发挥着至关重要的作用。Docker 提供了快速、可靠和易于使用的容器化解决方案&#xff0c;使开发人员和 DevOps 专业人士能够以轻松的方式将应用程序从一个环境部署到另一个环…

Centos7 K8S 集群 - kubeadm搭建方式

机器准备 搭建环境是centos7, 四核心4G内存四台机器 一个master节点&#xff0c;一个etcd&#xff0c;两台node 机器名称IP 地址master192.168.1.128node1192.168.1.129node2192.168.1.130node3192.168.1.131 机器时间同步 各节点时间要求精确同步&#xff0c;可以直接联网…

如何在极狐GitLab 启用依赖代理功能

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何在[极狐GitLab…

VUE3 + Elementui-Plus 之 树形组件el-tree 一键展开(收起);一键全选(不全选)

需求&#xff1a; 产品要求权限树形结构添加外部复选框进行全部展开或收起&#xff1b;全选或不全选。 实现步骤&#xff1a; tree组件部分&#xff1a; <div class"role-handle"><div>权限选择(可多选)</div><div><el-checkbox v-mode…

Harmony鸿蒙南向驱动开发-MIPI DSI

功能简介 DSI&#xff08;Display Serial Interface&#xff09;是由移动行业处理器接口联盟&#xff08;Mobile Industry Processor Interface (MIPI) Alliance&#xff09;制定的规范&#xff0c;旨在降低移动设备中显示控制器的成本。它以串行的方式发送像素数据或指令给外…