本站提倡有节制游戏,合理安排游戏时间,注意劳逸结合。

【手游端源码】【彩缘源码】【螺旋丸源码】sync的源码_synchronized源码

2024-11-30 06:38:13 来源:百科 分类:百科

1.PyTorch 源码解读之 BN & SyncBN:BN 与 多卡同步 BN 详解
2.golang的码s码对象池sync.pool源码解读
3.Go并发编程 — sync.Once
4.图解Go里面的WaitGroup了解编程语言核心实现源码
5.从项目的一个 panic 说起:Go 中 Sync 包的分析应用
6.Go并发编程:goroutine,channel和sync详解

sync的源码_synchronized源码

PyTorch 源码解读之 BN & SyncBN:BN 与 多卡同步 BN 详解

       BatchNorm原理

       BatchNorm最早在全连接网络中提出,旨在对每个神经元的码s码输入进行归一化操作。在卷积神经网络(CNN)中,码s码这一原理被扩展为对每个卷积核的码s码输入进行归一化,即在channel维度之外的码s码所有维度上进行归一化。BatchNorm带来的码s码手游端源码优势包括提高网络的收敛速度、稳定训练过程、码s码减少过拟合现象等。码s码

       BatchNorm的码s码数学表达式为公式[1],引入缩放因子γ和移位因子β,码s码作者在文章中解释了它们的码s码作用。

       PyTorch中与BatchNorm相关的码s码类主要位于torch.nn.modules.batchnorm模块中,包括如下的码s码类:_NormBase、BatchNormNd。码s码

       具体实现细节如下:

       _NormBase类定义了BN相关的码s码一些属性。

       初始化过程。

       模拟BN的forward过程。

       running_mean、running_var的更新逻辑。

       γ、β参数的更新方式。

       BN在eval模式下的行为。

       BatchNormNd类包括BatchNorm1d、BatchNorm2d、彩缘源码BatchNorm3d,它们的区别在于检查输入的合法性,BatchNorm1d接受2D或3D的输入,BatchNorm2d接受4D的输入,BatchNorm3d接受5D的输入。

       接着,介绍SyncBatchNorm的实现。

       BN性能与batch size密切相关。在batch size较小的场景中,如检测任务,内存占用较高,单张显卡难以处理较多,导致BN效果不佳。SyncBatchNorm提供了解决方案,其原理是所有计算设备共享同一组BN参数,从而获得全局统计量。

       SyncBatchNorm在torch/nn/modules/batchnorm.py和torch/nn/modules/_functions.py中实现,前者负责输入合法性检查以及参数设置,后者负责单卡统计量计算和进程间通信。

       SyncBatchNorm的forward过程。

       复习方差计算方式。

       单卡计算均值、方差,螺旋丸源码进行归一化处理。

       同步所有卡的数据,得到全局均值mean_all和逆标准差invstd_all,计算全局统计量。

       接着,介绍SyncBatchNorm的backward过程。

       在backward过程中,需要在BN前后进行进程间通信。这在_functions.SyncBatchNorm中实现。

       计算weight、bias的梯度以及γ、β,进一步用于计算梯度。

golang的对象池sync.pool源码解读

       在编程实践中,对象池sync.pool的出现是为了优化频繁创建和销毁对象带来的性能问题。它解决了新对象创建时的内存分配和垃圾回收(GC)压力。对象池的核心思想是复用已经创建的对象,避免不必要的资源消耗。

       对象池的应用范围广泛,如连接池、线程池等,它们都是通过池化来复用资源,减少创建和销毁的task函数源码开销,提升服务响应速度。实际上,缓存也是类似的概念,通过存储已计算结果,减少重复计算,加快服务响应。

       go1.版本的对象池原理涉及一个简单的结构体,通过Get和Put函数来管理对象。创建对象池时,需要传入一个创建新对象的函数。池中的对象存储在local数组中,每个goroutine的P都有对应的池,以减少锁竞争。pin和unpin函数用于管理和抢占P,以控制资源的使用。

       在GC过程中,对象池会在每次清理前清空,以防止内存溢出。go1.版本引入了victim cache机制,通过双向链表优化了对象的获取和存储,减少锁竞争,提升性能。

       总结来说,shopee商城源码对象池的关键在于复用和预分配,通过技术手段减少创建、减少GC压力,并利用缓存提高响应速度。理解这些原理对于优化程序性能和资源管理至关重要。

Go并发编程 — sync.Once

       ç®€ä»‹

       Once 可以用来执行某个函数,但是这个函数仅仅只会执行一次,常常用于单例对象的初始化场景。说到这,就不得不说一下单例模式了。

单例模式

       å•ä¾‹æ¨¡å¼æœ‰æ‡’汉式和饿汉式两种,上代码。

饿汉式

       é¥¿æ±‰å¼é¡¾åæ€ä¹‰å°±æ˜¯æ¯”较饥饿,所以就是上来就初始化。

var?instance?=?&Singleton{ }type?Singleton?struct?{ }func?GetInstance()?*Singleton?{ return?instance}懒汉式

       æ‡’汉式顾名思义就是偷懒,在获取实例的时候在进行初始化,但是懒汉式会有并发问题。并发问题主要发生在 instance == nil 这个判断条件上,有可能多个 goruntine 同时获取 instance 对象都是 nil ,然后都开始创建了 Singleton 实例,就不满足单例模式了。

var?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}加锁

       æˆ‘们都知道并发问题出现后,可以通过加锁来进行解决,可以使用 sync.Metux 来对整个方法进行加锁,就例如下面这样。这种方式是解决了并发的问题,但是锁的粒度比较高,每次调用 GetInstance 方法的时候都需要获得锁才能获得 instance 实例,如果在调用频率比较高的场景下性能就不会很好。那有什么方式可以解决嘛?让我们接着往下看吧

var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ mutex.Lock()defer?mutex.Unlock()if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}Double Check

       ä¸ºäº†è§£å†³é”çš„粒度问题,我们可以使用 Double Check 的方式来进行解决,例如下面的代码,第一次判断 instance == nil 之后需要进行加锁操作,然后再第二次判断 instance == nil 之后才能创建实例。这种方式对比上面的案例来说,锁的粒度更低,因为如果 instance != nil 的情况下是不需要加锁的。但是这种方式实现起来是不是比较麻烦,有没有什么方式可以解决呢?

var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?mutex.Lock()?defer?mutex.Unlock()?if?instance?==?nil?{ ?instance?=?&Singleton{ }?}}return?instance}使用 sync.Once

       å¯ä»¥ä½¿ç”¨ sync.Once 来实现单例的初始化逻辑,因为这个逻辑至多只会跑一次。推荐使用这种方式来进行单例的初始化,当然也可以使用饿汉式。

var?once?sync.Oncevar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ once.Do(func()?{ ?instance?=?&Singleton{ }})return?instance}源码分析

       ä¸‹é¢å°±æ˜¯ sync.Once 包的源码,我删除了注释,代码不多,Once 数据结构主要由 done 和 m 组成,其中 done 是存储 f 函数是否已执行,m 是一个锁实例。

type?Once?struct?{ done?uint?//?f函数是否已执行mMutex?//?锁}func?(o?*Once)?Do(f?func())?{ if?atomic.LoadUint(&o.done)?==?0?{ ?o.doSlow(f)}}func?(o?*Once)?doSlow(f?func())?{ o.m.Lock()defer?o.m.Unlock()if?o.done?==?0?{ ?defer?atomic.StoreUint(&o.done,?1)?f()}}

       Do 方法

       ä¼ å…¥ä¸€ä¸ª function,然后 sync.Once 来保证只执行一次,在 Do 方法中使用 atomic 来读取 done 变量,如果是 0 ,就代码 f 函数没有被执行过,然后就调用 doSlow方法,传入 f 函数

       doShow 方法

       doShow 的第一个步骤就是先加锁,这里加锁的目的是保证同一时刻是能由一个 goruntine 来执行 doSlow 方法,然后再次判断 done 是否是 0 ,这个判断就相当于我们上面说的 DoubleCheck ,因为 doSlow 可能存在并发问题。然后执行 f 方法,然后执行使用 atomic 将 done 保存成 1。使用 DoubleCheck 保证了 f 方法只会被执行一次。

       æŽ¥ç€çœ‹ï¼Œé‚£å¯ä»¥è¿™æ ·å®žçŽ° sync.Once 嘛?

       è¿™æ ·ä¸æ˜¯æ›´ç®€å•ä¸€ç‚¹å˜›ï¼Œä½¿ç”¨åŽŸå­çš„ CAS 操作就可以解决并发问题呀,并发只执行一次 f 方法的问题是可以解决,但是 Do 方法可能并发,第一个调用者将 done 设置成了 1 然后调用 f 方法,如果 f 方法特别耗时间,那么第二个调用者获取到 done 为 1 就直接返回了,此时 f方法是没有执行过第二次,但是此时第二个调用者可以继续执行后面的代码,如果后面的代码中有用到 f 方法创建的实例,但是由于 f 方法还在执行中,所以可能会出现报错问题。所以官方采用的是Lock + DoubleCheck 的方式。

if?atomic.CompareAndSwapUint(&o.done,?0,?1)?{ f()}拓展

       æ‰§è¡Œå¼‚常后可继续执行的Once

       çœ‹æ‡‚了源码之后,我们就可以扩展 sync.Once 包了。例如 f 方法在执行的时候报错了,例如连接初始化失败,怎么办?我们可以实现一个高级版本的 Once 包,具体的 slowDo 代码可以参考下面的实现

func?(o?*Once)?slowDo(f?func()?error)?error?{ o.m.Lock()defer?o.m.Unlock()var?err?errorif?o.done?==?0?{ ?//?Double?Checkerr?=?f()if?err?==?nil?{ ?//?没有异常的时候记录done值atomic.StoreUint(&o.done,?1)}}return?err}

       å¸¦æ‰§è¡Œç»“果的 Once

       ç”±äºŽ Once 是不带执行结果的,我们不知道 Once 什么时候会执行结束,如果存在并发,需要知道是否执行成功的话,可以看下下面的案例,我这里是以 redis 连接的问题来进行说明的。Do 方法执行完毕后将 init 值设置成 1 ,然后其他 goruntine 可以通过 IsConnetion 来获取连接是否建立,然后做后续的操作。

type?RedisConn?struct?{ once?sync.Onceinit?uint}func?(this?*RedisConn)?Init()?{ this.once.Do(func()?{ ?//?do?redis?connection?atomic.StoreUint(&this.init,?1)})}func?(this?*RedisConn)?IsConnect()?bool?{ ?//?另外一个goroutinereturn?atomic.LoadUint(&this.init)?!=?0}

图解Go里面的WaitGroup了解编程语言核心实现源码

       sync.WaitGroup核心实现逻辑简单,主要用于等待一组goroutine退出。它通过Add方法指定等待的goroutine数量,Done方法递减计数。计数为0时,等待结束。sync.WaitGroup内部使用了一个state1数组,其中只有一个元素,类型为[3]uint。这是为了内存对齐,确保数据按照4字节对齐,从而在位和位平台间兼容。

       内部元素采用uint类型进行计数,长度为8字节。这是为了防止在位平台上对字节的uint操作可能不是原子的情况。使用uint保证了原子操作的执行和性能。在CPU缓存线(cache line)的上下文中,8字节长度可能有助于确保对缓存线的操作是原子的,从而避免数据损坏。

       测试8字节指针的构造,验证了在经过编译器进行内存分配对齐后,如果元素指针的地址不能被8整除,则其地址+4可以被8整除。这展示了编译器层内存对齐的实现细节。

       sync.WaitGroup中的8字节uint采用分段计数的方式,高位记录需要Done的数量,低位记录正在等待结束的计数。

       源码的核心原理包括使用位uint进行计数,通过高位记录需要Done的数量和低位记录等待的数量。当发现count>0时,Wait的goroutine会排队等待。任务完成后,goroutine执行Done操作,直到count==0,完成并唤醒所有等待的goroutine。

       计数与信号量的实现通过根据当前指针的地址确定采用哪个分段进行计数和等待。添加等待计数和Done完成等待事件分别对应sync.WaitGroup的Add和Done方法。等待所有操作完成时,sync.WaitGroup确保所有任务完成。

       为了深入理解这些概念,可以参考相关文章和资源,如关于CPU缓存线大小和原子操作的讨论。此外,更多源码分析文章可关注特定的公告号或网站,如www.sreguide.com。本篇文章由ArtiPub自动发布平台发布。

从项目的一个 panic 说起:Go 中 Sync 包的分析应用

       在项目开发过程中,遇到一个常见的错误——"fatal error: concurrent map read and map write",这是由于Golang内建的map在并发环境下不安全导致的。解决这个问题的方法并不复杂,就是转向使用sync包提供的并发安全的map。

       sync包在Golang 1.9之后被官方支持,其中包含了丰富的同步原语,是并发编程的关键部分。在Golang 1.9之前,解决map并发问题通常会借助sync包中的sync.RWMutex或其他锁机制。Golang作为支持用户态进程的编程语言,对并发编程的处理自然离不开锁,这是一种确保多个Goroutine在同一片内存中协同工作的同步机制。

       sync包的源码目录结构清晰,包含Mutex、RWmutex、WaitGroup、Map、Once、Cond、Pool等组件。接下来,我们将逐个分析这些同步原语的用途和使用注意事项,重点讨论在项目中常见的sync.Map。

       sync.Map是sync包中的一种高效并发安全的map实现,与内建map相比,它提供了Load、Store、LoadOrStore、Delete和Range等方法,并且具有更高的并发性能。虽然sync.Map没有len方法,但其内部机制使得在并发环境中的操作更加稳健。

       通过结合实际项目案例和面试题中的陷阱,本文简要探讨了sync包中Mutex、RWMutex、WaitGroup、Once以及Map的使用技巧和注意事项。在实际编程中,正确使用这些同步原语对于避免并发问题至关重要。

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

Golang sync.Cond 条件变量源码分析

       sync.Cond 是 Golang 标准库 sync 包中一个关键的条件变量类型,用于在多个goroutine间协调等待特定条件。它常用于生产者-消费者模型等场景,确保在某些条件满足后才能继续执行。本文基于 go-1. 源码,深入解析 sync.Cond 的核心机制与用法。

       sync.Cond 的基本用法包括创建条件变量、等待唤醒与发送信号。使用时,通常涉及到一个互斥锁(Locker)以确保并发安全性。首先,通过`sync.NewCond(l Locker)`创建条件变量。其次,`cond.Wait()`使当前执行的goroutine等待直到被唤醒,期间会释放锁并暂停执行。`cond.Signal()`和`Broadcast()`用于唤醒等待的goroutine,前者唤醒一个,后者唤醒所有。

       在底层实现中,sync.Cond 采用了一种称为 notifyList 的数据结构来管理等待和唤醒过程。notifyList 由一组元素构成,其中`wait`和`notify`表示当前最大ticket值和已唤醒的最大ticket值,而`head`和`tail`则分别代表等待的goroutine链表的头和尾。在`Wait`操作中,每次调用`runtime_notifyListAdd`生成唯一的ticket,并将当前goroutine添加到链表中。当调用`Signal`或`Broadcast`时,会查找并唤醒当前`notify`值对应的等待goroutine,并更新`notify`值。

       信号唤醒过程确保了FIFO的顺序,即最早等待的goroutine会首先被唤醒。这种机制有效地防止了并发操作下列表的乱序,确保了正确的唤醒顺序,尽管在实际执行中,遍历整个列表的过程在大多数情况下效率较高。

       在使用sync.Cond时,需注意避免潜在的死锁风险和错误的唤醒顺序。确保合理管理互斥锁的使用,以及在适当情况下使用`Signal`或`Broadcast`来唤醒等待的goroutine。正确理解和应用sync.Cond,能有效提升并发编程的效率与稳定性。

相关推荐
一周热点