很多朋友对于copycheck的和读写锁的nocopy机制不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
在go中,每次读写时都需要加互斥锁,这个对程序的影响还是比较大的。所以我们在sync包中能够找到另外一个锁—读写锁。当然,读写锁适用于读次数远远多于写次数的场景。
那么读写锁和互斥锁有什么联系和不同呢?
typeRWMutexstruct{\n\twMutex\n\twriterSemuint32\n\treaderSemuint32\n\treaderCountint32\n\treaderWaitint32\n}
可以看到w是互斥锁,writerSem和readerSem都是等待者,readerCount是读计数器,readerWait是获取写锁需要等待的读锁释放数量。
而最多支持rwmutexMaxReaders(2^30个读计数器)
这里读写锁做了个很精妙的方法区分读写锁,如果有写锁进来,将readerCount-wmutexMaxReaders,因为readerCount最大数量是小于wmutexMaxReaders,所以在加锁结果过程中,如果发现readerCount<0,那么就知道有写锁加进来了。
每次goroutine获取读锁时,readerCount+1,然后分两种情况:
func(rw*RWMutex)RLock(){\n\tifatomic.AddInt32(&rw.readerCount,1)<0{\n\t\t//将goroutine排到队列尾部,挂起goroutine,监听readerSem信号量\n\t\truntime_SemacquireMutex(&rw.readerSem,false,0)\n\t}\n}2、读解锁
读解锁只会撤销对应的RLock调用,不会影响其他读锁
将readerCount-1,此时分为以下几种情况:
如果r>0,那么直接解锁,而对于r<0的情况,第三种和第四种是异常情况,不能用RUnlock解写锁,只能将readerCount-1,并唤醒等待的读锁,只有将所有读锁的goroutine全部释放,才会唤醒写锁。
func(rw*RWMutex)RUnlock(){\n\tifr:=atomic.AddInt32(&rw.readerCount,-1);r<0{\n\t\t//Outlinedslow-pathtoallowthefast-pathtobeinlined\n\t\trw.rUnlockSlow(r)\n\t}\n}\n\nfunc(rw*RWMutex)rUnlockSlow(rint32){\n\tifr+1==0||r+1==-rwmutexMaxReaders{\n\t\tthrow("sync:RUnlockofunlockedRWMutex")\n\t}\n\tifatomic.AddInt32(&rw.readerWait,-1)==0{\n\t\truntime_Semrelease(&rw.writerSem,false,1)\n\t}\n}3、写加锁
写操作是互斥的,所以写操作是需要添加互斥锁,然后通知其他读锁,如果有读锁,就挂起写锁
func(rw*RWMutex)Lock(){\n\trw.w.Lock()\n\tr:=atomic.AddInt32(&rw.readerCount,-rwmutexMaxReaders)+rwmutexMaxReaders\n\tifr!=0&&atomic.AddInt32(&rw.readerWait,r)!=0{\n\t\truntime_SemacquireMutex(&rw.writerSem,false,0)\n\t}\n}4、写解锁
同样,写解锁向读锁发出通知,还原加锁的readerCount
func(rw*RWMutex)Unlock(){\n\tr:=atomic.AddInt32(&rw.readerCount,rwmutexMaxReaders)\n\tifr>=rwmutexMaxReaders{\n\t\tthrow("sync:UnlockofunlockedRWMutex")\n\t}\n\tfori:=0;i<int(r);i++{\n\t\truntime_Semrelease(&rw.readerSem,false,0)\n\t}\n\trw.w.Unlock()\n}三、nocopy
因为是需要加锁解锁操作,所以在goroutine中是不能使用拷贝的。注释中也明确指定了这一点:
ARWMutexmustnotbecopiedafterfirstuse.
如果我们在代码中使用会出现异常情况。
如果结构体对象包含指针字段,当该对象被拷贝时,会使得两个对象中的指针字段变得不再安全。
typeSstruct{\n\tf1int\n\tf2*s\n}\n\ntypesstruct{\n\tnamestring\n}\n\nfuncmain(){\n\tmOld:=S{\n\t\tf1:0,\n\t\tf2:&s{name:"mike"},\n\t}\n\tmNew:=mOld//拷贝\n\tmNew.f1=1\n\tmNew.f2.name="jane"\n\n\tfmt.Println(mOld.f1,mOld.f2)//输出:0&{jane}\n}
这时修改mNew的字段值会把mOld字段值修改掉,这就可能会引发安全问题。
funcmain(){\n\tvarastrings.Builder\n\ta.Write([]byte("a"))\n\tb:=a\n\tb.Write([]byte("b"))\n}
这段代码运行会报错
panic:strings:illegaluseofnon-zeroBuildercopiedbyvalue
这时因为它在内部实现了copyCheck方法
//Donotcopyanon-zeroBuilder.\ntypeBuilderstruct{\n\taddr*Builder//ofreceiver,todetectcopiesbyvalue\n\tbuf[]byte\n}\n\nfunc(b*Builder)Write(p[]byte)(int,error){\n\tb.copyCheck()\n\tb.buf=append(b.buf,p...)\n\treturnlen(p),nil\n}\n\nfunc(b*Builder)copyCheck(){\n\tifb.addr==nil{\n\t\tb.addr=(*Builder)(noescape(unsafe.Pointer(b)))\n\t}elseifb.addr!=b{\n\t\tpanic("strings:illegaluseofnon-zeroBuildercopiedbyvalue")\n\t}\n}
实现了逻辑也比较简单,b.addr指向了自身的指针,如果将a赋值给b,那么a和b本身是不同的对象,因此b.addr实际会指向a导致panic。
这里的noescape里面就有关于逃逸分析的内容
那有没有更简单的方式呢?有,就是互斥锁的接口
typenoCopystruct{}\nfunc(*noCopy)Lock(){}\nfunc(*noCopy)Unlock(){}
sync包中都存在nocopy检查,通过govet进行copy检查,都是添加这种类型。
也就是说,我们在代码中也使用这种方式,可以进行nocopy检查。
好了,文章到这里就结束啦,如果本次分享的copycheck的和读写锁的nocopy机制问题对您有所帮助,还望关注下本站哦!