2026/1/16 1:46:53
网站建设
项目流程
有了云服务器怎么建设网站,网页设计案例分析ppt,米拓cms,上海企业网络专线现在有一个命题作文#xff0c;需要一个结构体#xff0c;该结构体具有一个方法#xff0c;方法的传参是一个函数#xff0c;比如数据库客户端的初始化#xff0c;需要保证无论如何或者多次调用该方法#xff0c;传入的 函数只会执行一次#xff0c;即数据库客户端只初始…现在有一个命题作文需要一个结构体该结构体具有一个方法方法的传参是一个函数比如数据库客户端的初始化需要保证无论如何或者多次调用该方法传入的 函数只会执行一次即数据库客户端只初始化一次问该如何设计这个结构体。方案一 If-Else首先想到的就是在结构体内记录一下是否执行过传参的函数如果没有执行过就执行并且记录下来如果执行过就不再执行如此看来只是 if-else 而已写起来也非常顺畅代码如下。type Once struct {done bool}func (o *Once) Do(f func()) {if !o.done {o.done truef()}}但是这样做有一个问题在多协程并发的时候无法保证只执行一次。为何请听我细细讲解本质是因为对于 done 的操作不是原子操作多个协程执行顺序不确定若一个协程判定 !done 成立但尚未执行 f()此时如果有其他协程也判定了 !done 成立就会重复执行 f()流程图如下。f()Once实例协程B协程Af()Once实例协程B协程A初始状态: done false并发读取 done 字段f() 被执行了两次Do(f)Do(f)检查 !done (true)检查 !done (true)执行 f()执行 f()done truedone true方案二 CAS既然问题根源是 done 的判断和修改两个操作无法保证原子性那自然就会想到使用 Go 源码中的原子操作 CompareAndSwap后面简称为 CAS通过使用硬件的功能达成判断和修改两个操作的原子性。CAS 在 Go 中的应用十分广泛比如 Go 源码中的 sync.Mutex 本质就是 for 循环 CAS。type Once struct {done atomic.Uint32}func (o *Once) Do(f func()) {if o.done.CompareAndSwap(0, 1) {f()}}上面的问题算是告一段落了但是还有另外一个问题因为在我们的需求中 f 函数是一个前置操作通常情况下后面会紧接着执行其他操作比如访问数据库。同样是并发场景下一个协程 CAS 成功开始执行 f 但尚未执行完另外一个协程 CAS 失败就直接进行下一步而客户端这个时候还没初始化完成还没准备好就会访问数据库失败流程图如下。数据库操作f()初始化Once实例(CAS)协程B协程A数据库操作f()初始化Once实例(CAS)协程B协程ACAS成功 获得执行权CAS失败 直接返回初始化数据库客户端ing客户端尚未初始化完成Do(f) - CAS(0,1)Do(f) - CAS(0,1)开始执行f()访问数据库f()执行完成正常访问数据库方案三 Mutex其实就是需要在上面的基础上增加一个等待机制那说到阻塞或者等待资源释放第一个想到的就是 Go 源码中的 sync.Mutex互斥锁的作用是同一时间只能有一个协程持有锁去判断和操作 done其他协程拿不到锁都会阻塞于是第一个拿到锁的协程会执行初始化后面并发过来的协程就乖巧地静静等待。type Once struct {done boolm sync.Mutex}func (o *Once) Do(f func()) {o.m.Lock()defer o.m.Unlock()if o.done false {f()o.done true}}这样一来完全足以满足我们的需求流程图如下。数据库f()初始化Once实例(带Mutex)协程B协程A数据库f()初始化Once实例(带Mutex)协程B协程ADo(f) - 获取锁Do(f) - 尝试获取锁检查 done false (true)执行f()初始化done true释放锁成功获得锁检查 done true释放锁访问数据库访问数据库方案四 Mutex Atomic一旦初始化之后就不需要再初始化了而之后每次执行 Do 时还都去获取锁实在太浪费了要知道高并发下获取锁的代价是很高的。自然而然就会想到 CAS 的操作消耗很少那在获取锁的流程之前先 CAS 一下不就好了虽然在初始化前是多了一步操作但是毕竟只要初始化完成之后就会只走 CAS 的逻辑了所以对于长时间持续运行的程序来熟还是更优的。type Once struct {done atomic.Uint32m sync.Mutex}func (o *Once) Do(f func()) {if o.done.Load() 0 {o.doSlow(f)}}func (o *Once) doSlow(f func()) {o.m.Lock()defer o.m.Unlock()if o.done.Load() 0 {defer o.done.Store(1)f()}}这里也有一些小技巧持有互斥锁的期间不用担心并发问题所以不再用 CAS 了但是需要提前判断 done所以依旧用了原子操作的 Load 和 Store。使用 defer 执行 Store(1) 是有些思考在里面的试想一下如果 f 发生了 panic如果不加 defer 那么 done 依旧是 0后面仍会重复执行 f这就违背了 f 只执行一次的初衷。而且 defer 的执行顺序是栈的顺序所以 Store(1) 先执行再释放锁这也是对的。至此恭喜你发明了 sync.Once!Sugar为了方便开发时快速使用 sync.Once Go 源码中还有下面有两个工具函数。OnceFunc基础款式需要保存 Once 结构体和 f 函数而宝宝款式 OnceFunc 将两者整合成了一个闭包函数可以作为全局变量直接调用可以小幅度减少代码量同时逻辑内敛减少阅读代码时的心智负担。只不过内部 oncevalidp 三个变量都内存逃逸了如果追求性能的话还是用基础款式比较好。func OnceFunc(f func()) func() {var (once Oncevalid boolp any)// Construct the inner closure just once to reduce costs on the fast path.g : func() {defer func() {p recover()if !valid {// Re-panic immediately so on the first call the user gets a// complete stack trace into f.panic(p)}}()f()f nil // Do not keep f alive after invoking it.valid true // Set only if f does not panic.}return func() {once.Do(g)if !valid {panic(p)}}}OnceValueOnceValue 则是在 OnceFunc 的基础上增加了一个返回值感兴趣的可以去看下源码这里就不多做介绍了// OnceValue returns a function that invokes f only once and returns the value// returned by f. The returned function may be called concurrently.//// If f panics, the returned function will panic with the same value on every call.func OnceValue[T any](f func() T) func() T