皮皮网
皮皮网

【云豹短视频源码二开版下载】【仙剑风rpg源码】【cdn建站系统源码】loking源码

来源:cctv钓鱼源码 发表时间:2024-12-22 15:54:31

1.Linux读写锁逻辑解析
2.spinlock(linux kernel 自旋锁)
3.Go并发编程:goroutine,channel和sync详解

loking源码

Linux读写锁逻辑解析

       Linux的源码读写锁机制,如同一把精密的源码多线程调和器,巧妙解决并发世界中的源码读多写少困境。其核心数据结构,源码如rwsem(读写信号量),源码包含读写状态counter和任务管理信息,源码云豹短视频源码二开版下载确保了读线程的源码并发性和写线程的互斥性。

       在内核设计中,源码当写线程尝试获取写锁时,源码可能会采取乐观自旋策略,源码若失败则会优雅地加入等待队列。源码rw_semaphore结构体中的源码关键成员,如task指针和队列,源码负责管理这些等待任务。源码对外API如down_read_trylock,源码为高效读取提供了可能,仙剑风rpg源码即使尝试失败也不会造成阻塞。

       读锁获取过程复杂而微妙,通过RWSEM_READER_BIAS快速路径和防止饿死的慢速路径,遵循公平原则。乐观偷锁机制允许临界区无写者时,高优先级读者尝试先入。若偷窃失败,读者会进入等待队列,队列超时机制确保效率与公平的平衡。

       当读线程加入等待队列,任务会被细致地处理,通过rwsem_add_waiter调整counter。特别是对于首位等待者,会设置RWSEM_FLAG_WAITERS标志。在尝试获取锁前,cdn建站系统源码可能需要唤醒潜在的等待者,如owner离开或读锁持有者。释放读锁时,仅简单地减去counter,不移除owner,以减少复杂性。

       写锁的获取则更为严谨,rwsem_write_trylock会检查rwsem状态,成功则立即持有并标记,否则返回。写锁的获取过程涉及等待队列的操作和唤醒策略,保证了高优先级的请求能及时响应。

       在写锁持有者释放时,与读锁类似,仅清理owner,delphi 截屏源码同时考虑writer可能对reader的抢锁影响。乐观自旋条件的判断,确保了在特定场景下的高效执行,如writer持有锁且未禁止自旋。

       OPPO内核团队在实际应用中,如手机交互场景,对Linux读写锁进行了优化,以降低延迟和提高吞吐量。深入研究5..内核源代码中的"Documentation\locking\"部分,你会发现更多优化细节。对于对技术感兴趣的读者,"内核工匠"公众号提供了丰富的技术内容。

       Linux的读写锁设计,如同一个精密的调和大师,它在并发世界中奏出了平衡、jeecg框架源码解析效率与公平的交响乐,无论在理论层面还是实际应用中,都展现出强大的适应性和灵活性,是多线程并发编程的有力工具。

spinlock(linux kernel 自旋锁)

       "锁"的使命在于保护临界资源,防止多CPU同时访问相同变量,避免数据一致性问题。非原子变量的修改无法在单指令周期内完成,若CPU1更改变量中途,CPU2访问该半成品变量,可能导致严重后果。解决方法是CPU1在修改变量前先"加锁",CPU2在访问变量前也加锁,由于已经被加锁,CPU2将原地等待锁释放。CPU1完成修改后"解锁",CPU2获取锁后加锁访问变量,完成后解锁。自旋锁(spinlock)与信号量、互斥锁不同,等待锁释放时不睡眠,而是自旋。不睡眠的优点是适用于中断上下文运行,且对于快速获取锁的场景效率更高。缺点是长时间等待锁释放会浪费大量CPU资源。

       spinlock适用于中断上下文和进程上下文,因此在内核中广泛应用。内核开发者关注其执行效率,spinlock方案经历了多次优化。简单介绍如下:

       首先,理解锁的优化,要从一个线程加锁,一千个线程等待锁的极端场景出发。有三种常见模式:

       1. CAS模式(Compare And Swap):

       通过原子变量控制。加锁时,获取变量为1,获取锁后修改为0。解锁时,将变量修改回1。优点是简单易懂,但存在竞争随机性,等待时间不确定。

       2. Ticket模式:

       解决CAS模式的随机竞争问题。锁包含tail值,加锁时保存本地变量,将tail+1,与锁的head对比,若相等则持有锁。解锁时,将head+1,所有等待的线程重新读取内存,确认是否轮到自己持有锁。优点是实现公平,先到先得,但浪费CPU资源。

       3.MCS模式(Mellor-Crummey and Scott):

       解决Ticket模式的CPU浪费问题。使用单向链表实现顺序通知,效率更高,但结构体中使用指针,占用内存空间。MCS相比Ticket多出4字节内存,但自旋锁在内核中频繁使用,追求极致效率的开发者不能容忍这种浪费。

       最终形态为qspinlock,基于MCS模式改进,节省内存开销,保持高效执行。使用步骤包括包含头文件、定义自旋锁、初始化、加锁解锁。API适应中断嵌套和进程混合加锁,不考虑中断抢占时有其他接口。源码路径包括kernel\locking\spinlock.c、kernel\locking\qspinlock.c和include\linux\spinlock.h。原理阐述较多,代码解析不多,简单查看spin的层级即可。spinlock方案的演进对开发者无感,内核开发者可以轻松升级spin方案。更多内容请参阅RTFSC专栏。

Go并发编程:goroutine,channel和sync详解

       ä¼˜é›…的并发编程范式,完善的并发支持,出色的并发性能是Go语言区别于其他语言的一大特色。

       åœ¨å½“今这个多核时代,并发编程的意义不言而喻。使用Go开发并发程序,操作起来非常简单,语言级别提供关键字go用于启动协程,并且在同一台机器上可以启动成千上万个协程。

       ä¸‹é¢å°±æ¥è¯¦ç»†ä»‹ç»ã€‚

goroutine

       Go语言的并发执行体称为goroutine,使用关键词go来启动一个goroutine。

       go关键词后面必须跟一个函数,可以是有名函数,也可以是无名函数,函数的返回值会被忽略。

       go的执行是非阻塞的。

       å…ˆæ¥çœ‹ä¸€ä¸ªä¾‹å­ï¼š

packagemainimport("fmt""time")funcmain(){ gospinner(*time.Millisecond)constn=fibN:=fib(n)fmt.Printf("\rFibonacci(%d)=%d\n",n,fibN)//Fibonacci()=}funcspinner(delaytime.Duration){ for{ for_,r:=range`-\|/`{ fmt.Printf("\r%c",r)time.Sleep(delay)}}}funcfib(xint)int{ ifx<2{ returnx}returnfib(x-1)+fib(x-2)}

       ä»Žæ‰§è¡Œç»“果来看,成功计算出了斐波那契数列的值,说明程序在spinner处并没有阻塞,而且spinner函数还一直在屏幕上打印提示字符,说明程序正在执行。

       å½“计算完斐波那契数列的值,main函数打印结果并退出,spinner也跟着退出。

       å†æ¥çœ‹ä¸€ä¸ªä¾‹å­ï¼Œå¾ªçŽ¯æ‰§è¡Œæ¬¡ï¼Œæ‰“印两个数的和:

packagemainimport"fmt"funcAdd(x,yint){ z:=x+yfmt.Println(z)}funcmain(){ fori:=0;i<;i++{ goAdd(i,i)}}

       æœ‰é—®é¢˜äº†ï¼Œå±å¹•ä¸Šä»€ä¹ˆéƒ½æ²¡æœ‰ï¼Œä¸ºä»€ä¹ˆå‘¢ï¼Ÿ

       è¿™å°±è¦çœ‹Go程序的执行机制了。当一个程序启动时,只有一个goroutine来调用main函数,称为主goroutine。新的goroutine通过go关键词创建,然后并发执行。当main函数返回时,不会等待其他goroutine执行完,而是直接暴力结束所有goroutine。

       é‚£æœ‰æ²¡æœ‰åŠžæ³•è§£å†³å‘¢ï¼Ÿå½“然是有的,请往下看。

channel

       ä¸€èˆ¬å†™å¤šè¿›ç¨‹ç¨‹åºæ—¶ï¼Œéƒ½ä¼šé‡åˆ°ä¸€ä¸ªé—®é¢˜ï¼šè¿›ç¨‹é—´é€šä¿¡ã€‚常见的通信方式有信号,共享内存等。goroutine之间的通信机制是通道channel。

       ä½¿ç”¨make创建通道:

ch:=make(chanint)//ch的类型是chanint

       é€šé“支持三个主要操作:send,receive和close。

ch<-x//发送x=<-ch//接收<-ch//接收,丢弃结果close(ch)//关闭无缓冲channel

       make函数接受两个参数,第二个参数是可选参数,表示通道容量。不传或者传0表示创建了一个无缓冲通道。

       æ— ç¼“冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作。相反,如果接收先执行,那么接收goroutine将会阻塞,直到另一个goroutine在对应通道上执行发送。

       æ‰€ä»¥ï¼Œæ— ç¼“冲通道是一种同步通道。

       ä¸‹é¢æˆ‘们使用无缓冲通道把上面例子中出现的问题解决一下。

packagemainimport"fmt"funcAdd(x,yint,chchanint){ z:=x+ych<-z}funcmain(){ ch:=make(chanint)fori:=0;i<;i++{ goAdd(i,i,ch)}fori:=0;i<;i++{ fmt.Println(<-ch)}}

       å¯ä»¥æ­£å¸¸è¾“出结果。

       ä¸»goroutine会阻塞,直到读取到通道中的值,程序继续执行,最后退出。

缓冲channel

       åˆ›å»ºä¸€ä¸ªå®¹é‡æ˜¯5的缓冲通道:

ch:=make(chanint,5)

       ç¼“冲通道的发送操作在通道尾部插入一个元素,接收操作从通道的头部移除一个元素。如果通道满了,发送会阻塞,直到另一个goroutine执行接收。相反,如果通道是空的,接收会阻塞,直到另一个goroutine执行发送。

       æœ‰æ²¡æœ‰æ„Ÿè§‰ï¼Œå…¶å®žç¼“冲通道和队列一样,把操作都解耦了。

单向channel

       ç±»åž‹chan<-int是一个只能发送的通道,类型<-chanint是一个只能接收的通道。

       ä»»ä½•åŒå‘通道都可以用作单向通道,但反过来不行。

       è¿˜æœ‰ä¸€ç‚¹éœ€è¦æ³¨æ„ï¼Œclose只能用在发送通道上,如果用在接收通道会报错。

       çœ‹ä¸€ä¸ªå•å‘通道的例子:

packagemainimport"fmt"funccounter(outchan<-int){ forx:=0;x<;x++{ out<-x}close(out)}funcsquarer(outchan<-int,in<-chanint){ forv:=rangein{ out<-v*v}close(out)}funcprinter(in<-chanint){ forv:=rangein{ fmt.Println(v)}}funcmain(){ n:=make(chanint)s:=make(chanint)gocounter(n)gosquarer(s,n)printer(s)}sync

       sync包提供了两种锁类型:sync.Mutex和sync.RWMutex,前者是互斥锁,后者是读写锁。

       å½“一个goroutine获取了Mutex后,其他goroutine不管读写,只能等待,直到锁被释放。

packagemainimport("fmt""sync""time")funcmain(){ varmutexsync.Mutexwg:=sync.WaitGroup{ }//主goroutine先获取锁fmt.Println("Locking(G0)")mutex.Lock()fmt.Println("locked(G0)")wg.Add(3)fori:=1;i<4;i++{ gofunc(iint){ //由于主goroutine先获取锁,程序开始5秒会阻塞在这里fmt.Printf("Locking(G%d)\n",i)mutex.Lock()fmt.Printf("locked(G%d)\n",i)time.Sleep(time.Second*2)mutex.Unlock()fmt.Printf("unlocked(G%d)\n",i)wg.Done()}(i)}//主goroutine5秒后释放锁time.Sleep(time.Second*5)fmt.Println("readyunlock(G0)")mutex.Unlock()fmt.Println("unlocked(G0)")wg.Wait()}

       RWMutex属于经典的单写多读模型,当读锁被占用时,会阻止写,但不阻止读。而写锁会阻止写和读。

packagemainimport("fmt""sync""time")funcmain(){ varrwMutexsync.RWMutexwg:=sync.WaitGroup{ }Data:=0wg.Add()fori:=0;i<;i++{ gofunc(tint){ //第一次运行后,写解锁。//循环到第二次时,读锁定后,goroutine没有阻塞,同时读成功。fmt.Println("Locking")rwMutex.RLock()deferrwMutex.RUnlock()fmt.Printf("Readdata:%v\n",Data)wg.Done()time.Sleep(2*time.Second)}(i)gofunc(tint){ //写锁定下是需要解锁后才能写的rwMutex.Lock()deferrwMutex.Unlock()Data+=tfmt.Printf("WriteData:%v%d\n",Data,t)wg.Done()time.Sleep(2*time.Second)}(i)}wg.Wait()}总结

       å¹¶å‘编程算是Go的特色,也是核心功能之一了,涉及的知识点其实是非常多的,本文也只是起到一个抛砖引玉的作用而已。

       æœ¬æ–‡å¼€å§‹ä»‹ç»äº†goroutine的简单用法,然后引出了通道的概念。

       é€šé“有三种:

       æ— ç¼“冲通道

       ç¼“冲通道

       å•å‘通道

       æœ€åŽä»‹ç»äº†Go中的锁机制,分别是sync包提供的sync.Mutex(互斥锁)和sync.RWMutex(读写锁)。

       goroutine博大精深,后面的坑还是要慢慢踩的。

       æ–‡ç« ä¸­çš„脑图和源码都上传到了GitHub,有需要的同学可自行下载。

       åœ°å€ï¼šgithub.com/yongxinz/gopher/tree/main/sc

       ä½œè€…:yongxinz

相关栏目:焦点