皮皮网
皮皮网

【batis源码】【物流erp系统源码】【我是猪源码】locking源码

来源:源码行数 发表时间:2024-12-22 13:40:31

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

locking源码

Linux读写锁逻辑解析

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

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

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

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

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

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

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

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

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

spinlock(linux kernel 自旋锁)

       在Linux内核的世界里,自旋锁spinlock犹如守护者,守护着数据的临界区,确保并发访问的有序性。它不依赖于睡眠,而是通过连续的CPU循环来尝试获取锁,这在中断处理和进程上下文中表现出了极高的效率,但也可能造成CPU资源的浪费。自旋锁有三种主要实现方式:CAS(Compare and Swap)模式,简单直接但竞争随机;Ticket模式,引入公平性但消耗CPU;而MCS(Multi-CPU Scalable)模式,是对Ticket模式的优化,通过链表通知减少了CPU空转,实现了更高的效率与内存利用。

       在Linux内核的广泛应用中,自旋锁的性能优化尤为重要,尤其是在多线程竞态的极端场景。例如,MCS模式虽然牺牲了一定的内存使用,但其高效性能使之成为首选。特别是针对内存密集型的应用,qspinlock的出现,通过一个位原子变量巧妙地管理locked、pending和tail,实现了内存节省和高效操作。然而,这种复杂性也意味着在编写和维护时需要更加谨慎。

       要使用自旋锁,只需在spinlock.h>中引入相关头文件,定义spinlock并调用spin_lock、spin_unlock进行加锁解锁。举个实例,当处理中断和进程混合的并发任务时,spinlock能够确保数据的一致性。内核提供了多种API,如spin_lock, spin_unlock用于无中断操作,spin_lock_irq, spin_unlock_irq则避免了中断的嵌套,spin_is_locked函数则用于检查锁的状态。

       源代码的精髓隐藏在kernel\locking\spinlock.c和qspinlock.c中,头文件位于include\linux\spinlock.h。最新的Linux kernel 5..5 stable tree中包含了这些实现。深入研究源码,你会发现自旋锁的实现层次结构,从spin_lock到do_raw_spin_trylock,再到arch_spin_trylock,映射着qspinlock等优化方案。

       对于内核开发者来说,自旋锁的优化是一个动态发展的领域,新的解决方案可能会不断涌现。想要深入了解,不妨关注我们的专业专栏RTFSC(Linux kernel源码轻松读),这里有丰富的原创内容,助你探索更深层次的内核世界。

相关栏目:知识