go定时任务(beego定时任务、cron),协程为什么总是先输出倒数第一个,golang支持i++不支持++i,golang的sync.Cond的用法,golang Map,sync.Mutex,sync.RWMutex,golang 端口扫描,golang汇编,DAU 日活用户数量 MAU月活,eMMC (emcc),golang汇编和反编译,VISCA协议(可见光),光圈
go定时任务(beego定时任务、cron)
beego定时任务
package main
import (
"fmt"
"time"
"github.com/astaxie/beego/toolbox"
)
var OriginpointTask *toolbox.Task
func main() {
GetTask()
toolbox.StartTask()
for {
}
}
func GetTask() {
OriginpointTask = toolbox.NewTask("myTask", "*/2 * * * * *",
func() error {
fmt.Println("hello victory")
return nil
})
toolbox.AddTask("myTask", OriginpointTask)
}
func Task2() {
fmt.Println(time.Now())
tk := toolbox.NewTask("myTask", "0 0 6 * * 0-6", func() error {
fmt.Println(time.Now())
fmt.Println("hello world")
fmt.Println(time.Now())
return nil
})
// err := tk.Run()
// if err != nil {
// fmt.Println(err)
// }
toolbox.AddTask("myTask", tk)
toolbox.StartTask()
time.Sleep(25 * time.Second)
toolbox.StopTask()
fmt.Println(time.Now())
}
cron
package main
import (
"fmt"
"time"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New()
c.AddFunc("@every 10s", func() {
fmt.Println("tick every 1 second")
})
// c.AddFunc("30 * * * *", func() {
// fmt.Println("Every hour on the half hour")
// })
// c.AddFunc("30 3-6,20-23 * * *", func() {
// fmt.Println("On the half hour of 3-6am, 8-11pm")
// })
// c.AddFunc("0 0 1 1 *", func() {
// fmt.Println("Jun 1 every year")
// })
c.Start()
time.Sleep(20* time.Second)
c.Stop()
// for {
// time.Sleep(time.Second)
// }
}
协程为什么总是先输出倒数第一个
- runtime.GOMAXPROCS() golang默认使用所有的cpu核
- runtime.GOMAXPROCS(1) 变成了单核单线程
GMP模型(G goroutine,M 工作线程,P processor 上下文或cpu),单核情况下,所有goroutine运行在同一个线程(M)中,线程维护一个上下文。
单核,P就绪后,开始执行。默认先执行的是最后一个创建的协程,然后再继续执行其他协程,此次是按顺序来的。
package main
import (
"fmt"
"runtime"
"sync"
)
// 协程为什么总是先输出倒数第一个
func main() {
// wg1()
wg2()
}
func wg1() {
wg := sync.WaitGroup{}
wg.Add(5)
for i := 0; i < 5; i++ {
go func(i int) {
defer wg.Done()
fmt.Println("%d", i)
}(i)
}
wg.Wait()
/*输出结果:
%d 4
%d 1
%d 0
%d 2
%d 3
*/
/*
runtime.GOMAXPROCS() golang默认使用所有的cpu核
*/
}
func wg2() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(6)
for i := 0; i < 5; i++ {
go func(i int) {
defer wg.Done()
fmt.Println("%d", i)
}(i)
}
go func() {
defer wg.Done()
fmt.Println("开始循环")
}()
wg.Wait()
/*输出结果:
开始循环
%d 0
%d 1
%d 2
%d 3
%d 4
*/
/*
runtime.GOMAXPROCS(1) 变成了单核单线程
GMP模型(G goroutine,M 工作线程,P processor 上下文或cpu),单核情况下,所有goroutine运行在同一个线程(M)中,线程维护一个上下文。
单核,P就绪后,开始执行。默认先执行的是最后一个创建的协程,然后再继续执行其他协程,此次是按顺序来的。
*/
}
golang支持i++不支持++i
func igreaterless() {
i := 1
fmt.Println("i", i) //1
i--
fmt.Println("i--", i)//0
// --i //expected statement, found '--'
}
golang的sync.Cond的用法
// 10个goroutine有一个发生panic或者停了让其他9个也同时停止
https://studygolang.com/articles/28072?fr=sidebar
使用场景:
我需要完成一项任务,但是这项任务需要满足一定条件才可以执行,否则我就等着。
- 循环或定时去获取,配合计数器
- channel适用于一对一,通知的方式,
sync.Cond就是用于实现条件变量的,是基于sync.Mutext的基础上,增加了一个通知队列,通知的线程会从通知队列中唤醒一个或多个被通知的线程。
Cond实现了一个条件变量,一个线程集合地,供线程等待或者宣布某事件的发生。
每个Cond实例都有一个相关的锁(一般是Mutex或RWMutex类型的值),它必须在改变条件时或者调用Wait方法时保持锁定。Cond可以创建为其他结构体的字段,Cond在开始使用后不能被拷贝。
主要有以下几个方法:
sync.NewCond(&mutex):生成一个cond,需要传入一个mutex,因为阻塞等待通知的操作以及通知解除阻塞的操作就是基于sync.Mutex来实现的。
sync.Wait():用于等待通知
sync.Signal():用于发送单个通知
sync.Broadcat():用于广播
package main
import (
"fmt"
"sync"
"time"
)
var locker sync.Mutex
var cond = sync.NewCond(&locker)
// NewCond(l Locker)里面定义的是一个接口,拥有lock和unlock方法。
// 看到sync.Mutex的方法,func (m *Mutex) Lock(),可以看到是指针有这两个方法,所以应该传递的是指针
func main() {
locker.Lock()
for i := 0; i < 10; i++ {
go func(x int) {
fmt.Println(x,"111111111")
cond.L.Lock() // 获取锁
fmt.Println(x,"22222222")
defer cond.L.Unlock() // 释放锁
// 通知到来的时候, cond.Wait()就会结束阻塞, do something. 这里仅打印
fmt.Println(x,"33333333")
cond.Wait() // 等待通知,阻塞当前 goroutine
fmt.Println(x,"444444444")
}(i)
}
time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 进入 Wait 阻塞状态
fmt.Println("Signal...")
cond.Signal() // 1 秒后下发一个通知给已经获取锁的 goroutine
time.Sleep(time.Second * 1)
fmt.Println("Signal...")
cond.Signal() // 1 秒后下发下一个通知给已经获取锁的 goroutine
time.Sleep(time.Second * 1)
cond.Broadcast() // 1 秒后下发广播给所有等待的goroutine
fmt.Println("Broadcast...")
time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 执行完毕
}
/*
9 111111111
0 111111111
1 111111111
2 111111111
3 111111111
4 111111111
5 111111111
6 111111111
7 111111111
8 111111111
Signal...
Signal...
Broadcast...
*/
package main
import (
"fmt"
"sync"
"time"
)
func main() {
mutex := sync.Mutex{}
var cond = sync.NewCond(&mutex)
mail := 1
go func() {
for count := 0; count <= 15; count++ {
time.Sleep(time.Second)
mail = count
cond.Broadcast()
}
}()
// worker1
go func() {
for mail != 5 { // 触发的条件,如果不等于5,就会进入cond.Wait()等待,此时cond.Broadcast()通知进来的时候,wait阻塞解除,进入下一个循环,此时发现mail != 5,跳出循环,开始工作。
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
}
fmt.Println("worker1 started to work")
time.Sleep(3 * time.Second)
fmt.Println("worker1 work end")
}()
// worker2
go func() {
for mail != 10 {
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
}
fmt.Println("worker2 started to work")
time.Sleep(3 * time.Second)
fmt.Println("worker2 work end")
}()
// worker3
go func() {
for mail != 10 {
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
}
fmt.Println("worker3 started to work")
time.Sleep(3 * time.Second)
fmt.Println("worker3 work end")
}()
// select {} //会出现死锁 fatal error: all goroutines are asleep - deadlock!
// for{}
time.Sleep(23 * time.Second)
}
/*
worker1 started to work
worker1 work end
worker3 started to work
worker2 started to work
worker3 work end
worker2 work end
*/
golang Map
https://mp.weixin.qq.com/s/B_ill8LantDW0QQjOtnsvA
介绍:
Map底层存储是基于哈希表(数组+链表)
哈希表存储原理(取模+拉链),
计算键的哈希值,根据哈希值取模得到索引,值存储到索引位置,如果索引相同,则通过链表再添加一个位置存放数据。
特点:
键不能重复,键必须可哈希
无序
线程不安全,设计者认为(我们不可能为了那小部分的需求,而牺牲大部分人的性能。)
底层原理:
hmap和bmap两个结构体实现
扩容:
初始化,写入数据,读取数据,扩容,迁移
线程不安全解决:
- 加读写锁,sync.RWMutex,适用于读多写少,互斥锁sync.Mutex也可以(sync synchronization 同步)
- sync.Map
sync.Map
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}
sync.Map 采用“空间换时间”的机制,sync.Map结构体冗余了两个数据结构,分别是:read和dirty;
写值的时候得通过 Store 方法,读的时候使用方法 Load 来读取。
与map+RWMutex的实现并发的方式相比,减少了加锁对应能的影响。
它做了一些优化:可以无锁访问read map,而且会优先操作read map,倘若只操作rede map就可以满足要求,那就不用去操作write map(dirty)。
所以在某些特定场景中它发生锁竞争的频率会远远小于map + RWMutex的实现方式。
优点:适合读多写少的场景;
缺点:如果是写多的场景,会导致read map缓存失效,需要加锁,冲突突变,性能急剧下降。
map并发写就用map+sync.Mutex效率更高,sync.Map性能比较低1580ns/op
珂哥讲技术:
sync.Map有两个数组,read数组和dirty数组(脏数组),写入map他会发现,read里面没有就写到dirty数组里面1:1,2:2 ,想读key=1的值发现read数组里面没有,加锁从dirty数组里面找,找到不到命中计数器++(miss计数器),当miss++>len(dirty),它就会把dirty里面的数据拷贝到read数组里面,然后把dirty里面的数据清理掉,读是不加锁特别快,
改写1:11,首先在read数组把11进行原子操作,数据交换,同时会在dirty数组里面写进去;写一个新的数据3会写到dirty数组里面,同样原理read的时候没有命中就去dirty数组里面数据拷贝到read数组里面,清理到dirty数组的全部数据 。所以为什么sync.Map读快是因为不加锁,写慢是因为加锁,同时在写的时候还会再去读一次read数组,造成时间消耗的比较多。
sync.Mutex
https://www.cnblogs.com/zhixlin/p/15170898.html 文字
https://www.bilibili.com/video/BV15V411n7fM?p=3 视频
一、结构体
type Mutex struct {
state int32 // 互斥锁的状态:被g持有,空闲等
sema uint32 // 信号量,用于阻塞/唤醒 goroutine(协程)
}
//使用
var mtx sync.Mutex
mtx.Lock()
mtx.Unlock()
这里的 state 字段是 int32 类型,但是它被分为了4 部分操作,为了使用更少的内存去表达锁的各个状态。
mutexLocked:锁状态标识,1为已加锁;
mutexWoken:记录是否已有goroutine被唤醒了,1为以唤醒;
mutexStarving:工作模式,0位正常模式,1位饥饿模式;
mutexWaiterShift:等于3表示出了最低3位以外,state的其他位用来记录有多少个等待者在排队;
锁的 state 是分为 4 部分使用的(通过位操作符做到的)
- waiter(29 bit): 尝试获取当前锁而陷入阻塞的等待者们
- starving(1 bit): 当前锁是 饥饿模式
- woken(1 bit): 当前锁是 Woken 状态,有该锁的等待这被唤醒
- locked(1 bit): 0 表示互斥锁是空闲的,1 表示互斥锁是被持有的
正常模式
正常模式下,一个尝试加锁的goroutine会先自旋几次,尝试通过原子操作获得锁,若几次自旋之后仍不能获得锁,则通过信号量排队等待。所有的等待者都会先按照先入先出(FIFO)的顺序排队。
但是锁被释放,第一个等待者被唤醒后并不会直接拥有锁,而是需要和后来者竞争,也就是那些处于自旋阶段,尚未排队等待的goroutine,这种情况下后来者更有优势,一方面,它们正在CPU上运行,自然比刚唤醒的goroutine更有优势,另一方面处于自旋状态的goroutine可以有很多,而被唤醒的goroutine每次只有一个。所以被唤醒的goroutine有更大概率拿不到锁,这种情况下它会被重新插入到队列的头部,而不是尾部。而当一个goroutine本次加锁等待的时间超过了1ms后,它会把当前goroutine从正常模式切换至饥饿模式。
饥饿模式
在饥饿模式下,Mutex的所有权从执行Unlock的goroutine直接传递给等待队列头部的goroutine, 后来者不会自旋,也不会尝试获得锁,即使Mutex处于Unloked状态,它们会直接从队列的尾部排队等待。当一个等待者获得锁之后,它会在以下两种情况时,将Mutex由饥饿模式切换回正常模式,一种情况是:它的等待时间小于1ms,也就是它刚来不久,第二种情况是:它是最后一个等待者,等待队列已经空了,后面自然就没有饥饿的goroutine了。
综上所述,在正常模式下自旋和排队同时存在的,执行Lock的goroutine会先一边自旋,尝试过几次后还没拿到锁,就需要去排队等待了。这种在排队之前先让大家来抢的模式,能够有更高的吞吐量,因为频繁的挂起、唤醒goroutine会带来较多的开销。但又不能无限制的自旋,要把自旋的开销控制在较小的范围内。所以在正常模式下,Mutex有更好的性能,但是可能会出现队列尾端的goroutine迟迟抢不到锁(尾端延迟)的情况。而饥饿模式下不在自旋尝试,所有goroutine都要排队,严格的先来后到,对于防止出现尾端延迟来讲特别重要。
二、Mutex.Lock()
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
// 走这条路,就可以将 Lock()方法内联到调用者函数的代码中去(因为此时Lock()函数的代码少),减少了函数栈的开辟和释放,提高了性能
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))//检测相关的代码
}
return
}
// Slow path (outlined so that the fast path can be inlined)
m.lockSlow()
}
主要通过atomic函数实现了Fast path,相应的Slow path被单独放在了lockSlow和unlockSlow方法中。 源码注释:这样是为了便于编译器对Fast path进行内联优化,
Lock方法的Fast path期望Mutex处于Unlocked状态,没有goroutine在排队,更不会饥饿,理想状况下,一个CAS操作就可以获得锁,但是CAS没能获得锁,就需要进入Slow path,也就是lockSlow方法。
Unlock方法同理,首先通过原子操作从state中减去mutexLocked,也就是释放锁,然后根据state的新值来判断是否需要执行Slow path。如果新值为0,也就意味着没有其他goroutine在排队,所以不需要执行额外操作,如果新值不为0,那就需要进入Slow path,看看是不是需要唤醒某个goroutine。
atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
这个是原子操作
原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).
所谓原子操作就是: 虽然有 cache 的存在,但是单个核上的单个指令进行原子操作的时候,能确保其它处理器或者核不访问此原子操作的地址,或者是确保其它处理器或者核总是访问原子操作之后的最新的值。
其次由于缓存的存在,在多核情况下,会出现各个 cpu 读取数据不一致的问题(因为总是先从缓存中读取数据),为了解决这个问题,使用了一种叫做内存屏障的方法。一个写内存屏障会告诉 cpu,必须要要等到它(内存屏障)管道中的未完成数据都刷新到内存中,再进行操作。而且此操作会让相关的 cpu 的缓存行失效(cpu 缓存一致性协议)。
Golang atomic包中实现了内存屏障的功能。所以 atomic 包中的方法,1.实现了原子操作,2.有内存屏障的功能。两者确保了 cpu 使用该方法操作的值,其他 cpu 总是能读到最新值。当然这个不能常用,毕竟废掉了缓存,以我的见解,也只有并发的时候才用到。
刚刚是锁状态刚好为 0 的时候,若是锁被其他 g 所持有,那么将进入 lockSlow
方法。
为什么拿不到锁不直接进行睡眠呢💤?以前的版本的确是这样设计的:每次都把拿不到锁的 g 加入队列,然后持有锁的 g 释放后会唤醒队列中的 g。 但是出于性能的考虑,释放锁时将 g 交给正在占用 cpu 时间的 g 将能提高性能,这个 g 也就是刚好来请求锁的 g。但是想要直接交给这个 g 的条件也是十分苛刻的。
同时锁因为允许新来的 g 与醒来的 g 进行竞争,可能导致醒来的 g 获取不到锁,而导致阻塞的 g 可能长时间阻塞。这就是锁饥饿问题,毕竟一个刚醒来的 g 与 多个请求锁的 g 斗争 ,1 v n,胜率不大,所以引入锁的饥饿模式,即醒来的 g 让新来的 g 都乖乖加入阻塞队列去,不要与自己竞争。直到阻塞队列中的最后一个 g 发现自己是最后使用锁的 g 了,就解除锁的饥饿模式。
我们能得出以下的结论:
- normal 模式下:
- 加入了竞争的情况,被唤醒的 g 可能会与刚到来的 g 一起竞争锁,但是被唤醒的 g 很可能失败。因为 g 被唤醒就说明锁已经被释放了,那么自旋的很可能已经获得锁了
- 睡眠时间超过 1ms 的 g,被唤醒后想要将 mutex 切换为 starving 模式,切换后也会再次进入阻塞队列且排在队列头部,等待锁的释放别唤醒
- starving 模式下:
- 只有被唤醒的等待者才能加锁,其他的 g 全都进入 FIFO 阻塞队列
谨记:在原子操作处,所有的 g 都会是串行化的
串行化和并行化
串行化也叫做序列化,就是把存在于内存的对象数据转化成可以保存成硬盘文件的形式去存储;
并行化也叫反序列化,就是把序列化后的硬盘文件加载到内存,重新变成对象数据.
也就是把内存中对象数据变成硬盘文件.
三、Mutex.UnLock()
注意:
Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至今。
所以尽量做到谁 Lock 谁 UnLock,做到谁申请,就由谁释放。一般都是在同一个方法中使用。
还有 runtime_SemacquireMutex
这个请求信号量函数底层是用 gopark
函数实现的,而 gopark
函数:
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
if reason != waitReasonSleep {
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
}
// 请求获取当前协程的 M
mp := acquirem()
// 获取 M 关联的 G
gp := mp.curg
status := readgstatus(gp)
if status != _Grunning && status != _Gscanrunning {
throw("gopark: bad g status")
}
mp.waitlock = lock
mp.waitunlockf = unlockf
gp.waitreason = reason
mp.waittraceev = traceEv
mp.waittraceskip = traceskip
releasem(mp)
// can't do anything that might move the G between Ms here.
mcall(park_m)
}
该函数主要作用有三大点:
- 调用
acquirem
函数:
- 获取当前 goroutine 所绑定的 m,设置各类所需数据。
- 调用 `releasem` 函数**将当前 goroutine 和其 m 的绑定关系解除**。
- 调用
park_m
函数:
- 将当前 goroutine 的状态从 `_Grunning` 切换为 `_Gwaiting`,也就是等待状态。
- 删除 m 和当前 goroutine m->curg(简称gp)之间的关联。
- 调用
mcall
函数,仅会在需要进行 goroutine 切换时会被调用:
- 切换当前线程的堆栈,从 g 的堆栈切换到 g0 的堆栈并调用 fn(g) 函数。
- 将 g 的当前 PC/SP 保存在 g->sched 中,以便后续调用 goready 函数时可以恢复运行现场。
sync.RWMutex
https://www.cnblogs.com/zhixlin/p/15221829.html
读写锁在表现上是允许并发读,独占写的。这把锁理解上可以看成一把读锁(共享锁),一把写锁(独占锁),适用于读多写少的场景。
即调用读写锁时,一读线程持有读锁(RLock()),同时允许其它线程持有读锁,大家一起进行并发读。但是写线程(Lock())持有的写锁是独占锁的,当别人持有读或写锁,它就无法请求获得写锁。
总结:
writer 线程持有读写锁,这把锁就是写锁-独占锁
reader 线程持有读写锁,这把锁就是读锁-共享锁
一、结构体
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // reader 完成后会释放 writer信号量
readerSem uint32 // writer 完成后会释放 reader 信号量
readerCount int32 // 当前的 reader 的数量(以及用来判断是否有 writer 线程请求锁或者持有锁)
readerWait int32 // writer 等待 reader 完成的数量,到 0,writer 会被唤醒
}
const rwmutexMaxReaders = 1 << 30 // 表识最大 reader 线程数量
//使用
二、RLock
RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 当 reader 线程请求读锁,发现 readercout + 1 < 0
// 1. 为readerCount += 1
// 2. 判断 readerCount 是否小于 0
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// 证明有一个 writer 线程正在等待,阻塞自身,等待writer 线程完成
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
其中值得注意的是:
- readerCount 为负数,表示当前时刻有 writer 线程正在等待写锁 或者 持有写锁,因为写锁是独占锁,这种情况下,reader 线程应当进入阻塞等待 ,等待writer 线程完成释放 readerSem 信号量
- readerCount 为正数,表示此时 reader 线程的数量
三、RUnLock
Runlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// 将 readerCount 数量 - 1
// 判断此时 readerCount 数量是否小于 0
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// 如果此时 readerCount 数量小于 0,说明有 writer 线程等待锁
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// 如果此时 readerWait 数量,写线程[需要等待]的 reader 线程已经全部完成(不是 readerCount) -- readerWait == 0
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// 则释放 writerSem 信号量唤醒 writer 线程
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
值得注意的是:
readerWait 只有当 writer 线程请求锁时才会出现为非负数,此时它会拷贝readerCount 的大小,即 readerWait = readerCount, 如果这有 writer 线程,则 readerWait 的值为 0, readerCount 的大小为正数
总结:
当 writer 线程请求锁时,比它后来的 reader 线程不会影响到 readerWait 的数量,即 后面的 reader 线程是无法与 writer 线程竞争锁的,讲究先来后到,避免写锁饥饿。
四、Lock
Lock方法将rw锁定为写入状态,禁止其他线程读取或者写入。
值得注意的是,因为写锁是一把独占锁,所以所有的 wrtier 线程是竞争的关系,利用 RWMutex 的结构体成员 sync.Mutex 互斥锁保证
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 首先与其他 writer 线程竞争
rw.w.Lock()
// 通过将 readerCount 的数量变为负数,告诉 reader 线程这里有 writer 线程正在等待锁
// 注意这里的 r 是局部变量
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// 判断 r ,不为 0 说明有 reader 线程持有锁
// 且将此时的 r 赋值给 readerWait 成员,判断 readerWait 的数量
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// 请求写信号量,阻塞自身,等待 reader 线程释放 writer 信号量
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
值得注意的是:
当 writer 线程请求锁后,此时真正活跃的 reader 线程只有 readerWait 的数量,后面的 reader 线程都因为 readerCount 为负数(有 writer 线程请求锁或者持有锁)陷入阻塞状态。
直到 active reader 线程都执行完毕,则唤醒 writer 线程。
总结:想写必须等读完成才能唤醒写,后进来的读陷入阻塞状态。
五、UnLock
Unlock方法解除rw的写入锁状态,如果m未加写入锁会导致运行时错误。
func (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// 将 readerCount 数量变为正数,告诉 reader 线程,无 writer 线程了
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// 将此时所有的 reader 线程唤醒
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 释放互斥锁,唤醒 writer 线程竞争互斥锁
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}
golang 端口扫描
package main
import (
"flag"
"fmt"
"net"
"sync"
"time"
)
var lock sync.Mutex
func main() {
host := flag.String("host", "123.151.137.18", "hostname")
startPort := flag.Int("start_port", 1, "scanning start port")
endPort := flag.Int("end_port", 100, "scanning end port")
timeout := flag.Duration("timeout", time.Millisecond*200, "timeout")
flag.Parse()
ports := make([]int, 0, *endPort-*startPort+1)
wg := &sync.WaitGroup{}
for port := *startPort; port <= *endPort; port++ {
wg.Add(1)
go func(p int) {
opened := isOpen(*host, p, *timeout)
if opened {
lock.Lock()
ports = append(ports, p)
lock.Unlock()
}
wg.Done()
}(port)
}
wg.Wait()
fmt.Printf("opened ports: %v\n", ports)
}
func isOpen(host string, port int, timeout time.Duration) bool {
time.Sleep(time.Millisecond * 1)
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), timeout)
if err != nil {
return false
}
_ = conn.Close()
return true
}
golang汇编
go tool compile -N -l t1.go
go tool objdump t1.o
DAU 日活用户数量 MAU月活
DAU(Daily Active User)反映网站、互联网应用运营情况,结合MAU一起使用,用来衡量服务的用户粘度以及服务的衰退周期。
eMMC (emcc)
eMMC在封装中集成了一个控制器,提供标准接口并管理闪存;
结构:由一个嵌入式存储解决方案组成,带有MMC(多媒体卡)接口、快闪存储器设备及主控制器,接口速度高达400MBytes,具有快速、可升级的性能。
应用:对存储容量有较高要求的电子产品,Amazon kindle。
VISCA协议(可见光)
VISCA协议是SONY提出的云台控制协议,已经广泛应用于SONY的云台、摄像机和其他一些品牌的云台。VISCA比PECOL更先进,因为VISCA采用RS422传输,传输距离最远可达1200米。
以上的两种协议,传输的信令各不相同,详细的信令数据可以参考协议的标准,在实际应用中,我们还可以进行对两种不同协议的转换,可以通过硬件的方式也可以通过软件的方式进行转换。
-
快门:快门是通过控制光线进入相机时间的长短,进而控制进光量的。影响照片的亮度和影响记录的“轨迹”:可以定格瞬间,也可以记录过程。
相机中一般用1/400、8”这样的形式表示快门时间(也叫快门速度)。这个数值越大,快门开启的时间越长,进入相机的光线就越多;反之数值越小,进光量越少。
-
感光度又称为ISO,是指相机对光线的敏感程度。在光圈和快门不变的情况下,ISO越高,相机的感光能力越强,拍出来的照片就会越亮;反之就会越暗。
-
曝光:曝光三要素,光圈、快门、ISO,捕捉图像时图像显示的明/暗和清晰/模糊程度。
-
曝光增益:图像画面亮暗,摄像机传感器接收到原始景物的光后,在光电转换过程中,对原始的光进行调大或调小的过程。如果这时是增益调大了,我们的图象就会比实际的亮,相反就会更暗。如果传感器很小或质量不是很好,增益后就有很多的噪点。
-
白平衡:目的是为了让被摄场景中的物体能够在视频显示终端得到正确的色彩再现。白平衡模式,比如:自动、日光、阴影、阴天、钨丝灯、白色荧光灯、闪光灯等
-
R/B增益:增减画面红色、蓝色强度的设置。
-
数字降噪:降低高感光度所导致的更为明显的噪点。数字降噪(DNR),存在两种DNR – 2D和3D,3D降噪空域降噪和时域降噪。
-
伽马校正:是用来针对影片或是影像系统里对于光线的辉度或是三色刺激值所进行非线性的运算或反运算。
人类对光线或者黑白的感知,最大化地利用表示黑白的数据位或带宽。在通常的照明(既不是漆黑一片,也不是令人目眩的明亮)的情况下,人类的视觉大体有伽马或者是幂函数的性质。 -
变焦:拍摄时放大远方物体
光学变焦:分辨率及画质不会改变,画面清晰。数字变焦:将原先的图像尺寸裁小,让图像在LCD屏幕上变的比较大,清晰度下降。
-
聚焦:将光线汇聚,画面清晰;AF(Auto Focus)自动聚焦。
-
镜像:画面上下、左右、中心翻转。
-
日夜模式:画面颜色白天彩色,夜晚黑白。
-
防闪烁:检测来自荧光灯照明等人工光源的闪烁/闪动,并在闪烁的影响较少的时机拍摄影像。
该功能减少使用高速快门速度和连拍期间所拍摄影像的上部和下部之间由闪烁造成的曝光和色调差异。
-
电子防抖系统是指通过电子技术来实现防抖功能,通过对模糊图像分析,利用算法对影像进行补偿,这样势必会牺牲画面清晰度。电子防抖一般用在低端的相机和摄像机上。
电子防抖主要指在数码照相机上采用强制提高CCD感光参数同时加快快门并针对CCD上取得的图像进行分析,然后利用边缘图像进行补偿的防抖,电子防抖实际上是一种通过降低画质来补偿抖动的技术,此技术试图在画质和画面抖动之间取得一个平衡点。与光学防抖比较,此技术成本要低很多(实际上只需要对普通数码相机的内部软体作些调整就可做到),效果也要差。 -
光学防抖系统是通过移动镜片组或感光芯片的位置来补偿拍摄时相机的晃动,从而达到减震防抖的功能,使拍摄画面清晰、稳定。其防抖效果比电子防抖好,但缺点是成本高、费电,且需要一定空间。因此一般用在镜头倍数高画质要求严格的大型高端摄像机上。
-
制式切换 系统管理->本机设置->视频制式 50HZ/60HZ 视频模式 25601440@25fps
24FPS(Frames Per Second) 电影、动画
50HZ 1秒25祯 电视
60HZ 1秒30祯 海外
P制 N制
PAL制式使用的是720576,而NTSC制式使用的是720*480
P制:中、英、新西兰、澳大利亚
N制:美、日、韩、加拿大、菲律宾
衍射光栅(diffraction grating)是光栅的一种。它通过有规律的结构,使入射光的振幅或相位(或两者同时)受到周期性空间调制。
衍射光栅在光学上的最重要应用是作为分光器件,常被用于单色仪和光谱仪上。
- 阴影校正 自动 校正强度 0~100 /关闭
镜头补偿:补偿由于某些镜头特性导致的画面角落阴影或画面失真,或者减少画面角落处的色差。
阴影补偿:
设定是否自动补偿画面角落的阴影。([自动]/[关])
色差补偿:
设定是否自动减少画面角落处的色差。([自动]/[关])
失真补偿:
设定是否自动补偿画面的失真。([自动]/[关]) - 快门是相机上控制感光片有效曝光时间的一种装置。
参考:
https://www.zhihu.com/question/21427664
https://helpguide.sony.net/ilc/2035/v1/zh-cn/contents/TP1000284642.html
https://helpguide.sony.net/ilc/1720/v1/zh-cn/contents/TP0001663888.html
排查相机项目解决思路
代码注释没问题的话,排查前端,按F12看前端获取的数据,或者问是如何处理的。
golang汇编和反编译
https://mp.weixin.qq.com/s/7Bsmt9F-AZBjsIeVENo3ng
编译生成汇编
这是Go语言自带的功能,通过以下命令即可查看。当然还可以指定各种参数进行详细的研究,此处不再赘述。
- go tool compile -S x.go
- go build -gcflags -S x.go
反编译程序
go tool objdump
这是Go语言自带的反编译命令。
$ cat x.go
package main
func main() {
println(3)
}
$ go build x.go
$ go tool objdump -s main.main x
TEXT main.main(SB) /home/foo/codes/goinmemory/go_asm/x.go
x.go:3 0x45ec60 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
x.go:3 0x45ec69 483b6110 CMPQ 0x10(CX), SP
x.go:3 0x45ec6d 7637 JBE 0x45eca6
x.go:3 0x45ec6f 4883ec10 SUBQ $0x10, SP
x.go:3 0x45ec73 48896c2408 MOVQ BP, 0x8(SP)
x.go:3 0x45ec78 488d6c2408 LEAQ 0x8(SP), BP
x.go:4 0x45ec7d 0f1f00 NOPL 0(AX)
光圈
光圈:用来控制光线透过镜头,进入机身内感光面光量的装置;镜头内部加入多边形或圆形,并且面积可变的孔状光栅来达到控制镜头通光量。