draveness / blog-comments

面向信仰编程
https://draveness.me
140 stars 6 forks source link

Golang 并发编程与同步原语 · /golang-sync-primitives #151

Closed draveness closed 2 years ago

draveness commented 5 years ago

https://draveness.me/golang-sync-primitives

yuanjize commented 5 years ago

你这都出书了.....

draveness commented 5 years ago

你这都出书了.....

这也算出书么...

yuanjize commented 5 years ago

开源电子书😂

Me1onRind commented 5 years ago

老哥 厉害

ahjdzx commented 5 years ago

lockSlow 是你提出来的,还是源码封装好的。

draveness commented 5 years ago

lockSlow 是你提出来的,还是源码封装好的。

源码封装好的

wen-hemin commented 5 years ago

你好,想问下,cond的listen方法,看代码第一个协程获取锁后,调用wait方法等待。其他协程并不能获取到锁,也无法调用wait方法,是都被阻塞了吗

draveness commented 5 years ago

@wenhemin 你好,想问下,cond的listen方法,看代码第一个协程获取锁后,调用wait方法等待。其他协程并不能获取到锁,也无法调用wait方法,是都被阻塞了吗

是的,只能通过 Signal 和 Broadcast 唤醒锁,Wait 就是等待信号

yanjinbin commented 4 years ago

哇塞 这就是我想要的 一站式搞完 6666

yanjinbin commented 4 years ago

mutex的unlock 为什么要这么设计 ? 可以让别人unlock,然后自己再调用的花 就panic了 ?https://play.golang.org/p/k3qHjbwOFtF

draveness commented 4 years ago

mutex的unlock 为什么要这么设计 ? 可以让别人unlock,然后自己再调用的花 就panic了 ?https://play.golang.org/p/k3qHjbwOFtF

这个跟是不是其他线程解锁没关系吧,是不能解锁已经处于 unlocked 状态的互斥锁

yanjinbin commented 4 years ago

mutex的unlock 为什么要这么设计 ? 可以让别人unlock,然后自己再调用的花 就panic了 ?https://play.golang.org/p/k3qHjbwOFtF

这个跟是不是其他线程解锁没关系吧,是不能解锁已经处于 unlocked 状态的互斥锁

嗯 是的 听你这么说 没毛病,我把 锁的持有者 搞错了。 大佬,你再看下我之前在你Goroutine调度文章里面回复你的, 为什么 周期性 判断 %61==0 ,从global running G queue 里面取G 的问题?

tidyoux commented 4 years ago
  1. sync.Mutex.unlockSlow 部分有一处笔误:

如果互斥锁不存在等待者或者互斥锁的 mutexLockedmutexStarvingmutexWoken 状态都为 0,那么当前方法就可以直接返回,不需要唤醒其他等待者;

按照原代码,应为 不都为 0

  1. sync.noCopy 处的例子不太恰当:
sync.noCopy 是一个特殊的私有结构体,tools/go/analysis/passes/copylock 包中的分析器会在编译期间检查被拷贝的变量中是否包含 sync.noCopy 结构体,如果包含该结构体就会在运行时报出以下错误:

func main() {
    wg := sync.Mutex{}
    yawg := wg
    fmt.Println(wg, yawg)
}

$ go run proc.go
./prog.go:10:10: assignment copies lock value to yawg: sync.Mutex
./prog.go:11:14: call of fmt.Println copies lock value: sync.Mutex
./prog.go:11:18: call of fmt.Println copies lock value: sync.Mutex

因为,


2020-10-19 UPDATES: 增加了 lockType 相关的判断

leobuzhi commented 4 years ago
func main() {
    wg := sync.Mutex{}
    yawg := wg
    fmt.Println(wg, yawg)
}

这个编译器不会给报错,只会有警告。 同意楼上,你应该执行的是

go vet proc.go
bzd111 commented 4 years ago
func main() {
    wg := sync.Mutex{}
    yawg := wg
    fmt.Println(wg, yawg)
}

$ go run proc.go

这里的Mutex应该是WaitGroup吧 https://play.golang.org/p/j2n0gJghU9s

draveness commented 4 years ago
func main() {
  wg := sync.Mutex{}
  yawg := wg
  fmt.Println(wg, yawg)
}

这个编译器不会给报错,只会有警告。 同意楼上,你应该执行的是

go vet proc.go

已修复

func main() {
  wg := sync.Mutex{}
  yawg := wg
  fmt.Println(wg, yawg)
}

$ go run proc.go

这里的Mutex应该是WaitGroup吧 https://play.golang.org/p/j2n0gJghU9s

已修复

bzd111 commented 4 years ago

@draveness

func main() {
    wg := sync.Mutex{}
    yawg := wg
    fmt.Println(wg, yawg)
}

这个编译器不会给报错,只会有警告。 同意楼上,你应该执行的是

go vet proc.go

已修复

func main() {
    wg := sync.Mutex{}
    yawg := wg
    fmt.Println(wg, yawg)
}

$ go run proc.go

这里的Mutex应该是WaitGroup吧 https://play.golang.org/p/j2n0gJghU9s

已修复

下面的报错信息,没有改过来 ./proc.go:10:10: assignment copies lock value to yawg: sync.WaitGroup contains sync.noCopy ./proc.go:11:14: call of fmt.Println copies lock value: sync.WaitGroup contains sync.noCopy ./proc.go:11:18: call of fmt.Println copies lock value: sync.WaitGroup contains sync.noCopy

godliness commented 4 years ago

如果返回控制(空) — 所有 Goroutine 都成功执行;

draveness commented 4 years ago

如果返回控制(空) — 所有 Goroutine 都成功执行;

修复了

zhangzhitong0114 commented 4 years ago

会不会发生第二个lock的永远获取不到锁的情况呢?永远在等在信号量,如果从第三个开始每个都可以把mutexWoken设置为1(当然是符合条件的情况下),那么第二个是不是永远都获取不到锁

draveness commented 4 years ago

会不会发生第二个lock的永远获取不到锁的情况呢?永远在等在信号量,如果从第三个开始每个都可以把mutexWoken设置为1(当然是符合条件的情况下),那么第二个是不是永远都获取不到锁

这种问题不会出现吧,你能说一下出现这种问题时,多个线程的执行的指令顺序么

lmybill commented 4 years ago

点赞

douglarek commented 4 years ago

建议博主改改 sync.Cond 的例子,既然是条件变量,条件在哪呢?源码注释里的例子就很好嘛


2020-10-21 UPDATES:修改了 sync.Cond 例子,引入了条件

+var status int64
+
 func main() {
        c := sync.NewCond(&sync.Mutex{})
        for i := 0; i < 10; i++ {
                go listen(c)
        }
-       time.Sleep(1*time.Second)
+       time.Sleep(1 * time.Second)
        go broadcast(c)

        ch := make(chan os.Signal, 1)
@@ -589,13 +591,16 @@ func main() {

 func broadcast(c *sync.Cond) {
        c.L.Lock()
+       atomic.StoreInt64(&status, 1)
        c.Broadcast()
        c.L.Unlock()
 }

 func listen(c *sync.Cond) {
        c.L.Lock()
-       c.Wait()
+       for atomic.LoadInt64(&status) != 1 {
+               c.Wait()
+       }
        fmt.Println("listen")
        c.L.Unlock()
 }
douglarek commented 4 years ago

@wenhemin 你好,想问下,cond的listen方法,看代码第一个协程获取锁后,调用wait方法等待。其他协程并不能获取到锁,也无法调用wait方法,是都被阻塞了吗

是的,只能通过 Signal 和 Broadcast 唤醒锁,Wait 就是等待信号

这个并不一定吧;Wait 方法里面有 Unlock 和 Lock 的间隙在的,这个间隙里其他协程为什么不能获取锁?

func main() {
    c := sync.NewCond(&sync.Mutex{})
    var ready bool

    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Println("Ready: ", i)
            c.L.Lock()
            defer c.L.Unlock()
            fmt.Println("heheh")
            for !ready {
                c.Wait()
            }
            fmt.Println("Go: ", i)
        }(i)
    }

    go func() {
        c.L.Lock()
        defer c.L.Unlock()
        time.Sleep(4 * time.Second)
        fmt.Println("Now ready")
        ready = true
        c.Broadcast()
    }()

    ch := make(chan os.Signal, 1)
    signal.Notify(ch, os.Interrupt)
    <-ch
}
heheh
Ready:  5
Ready:  3
Ready:  4
Ready:  8
Ready:  6
Ready:  7
Ready:  9
heheh
heheh
heheh
heheh
heheh
heheh
Ready:  2
Now ready
Go:  3
heheh
Go:  9
heheh
Go:  2
Go:  4
Go:  8
Go:  6
Go:  7
Go:  1
Go:  0
Go:  5
draveness commented 4 years ago

建议博主改改 sync.Cond 的例子,既然是条件变量,条件在哪呢?源码注释里的例子就很好嘛

下一次 Review 的时候我看一下改不改

这个并不一定吧;Wait 方法里面有 Unlock 和 Lock 的间隙在的,这个间隙里其他协程为什么不能获取锁?

能详细解释一下么,没太看明白你的问题

douglarek commented 4 years ago

建议博主改改 sync.Cond 的例子,既然是条件变量,条件在哪呢?源码注释里的例子就很好嘛

下一次 Review 的时候我看一下改不改

这个并不一定吧;Wait 方法里面有 Unlock 和 Lock 的间隙在的,这个间隙里其他协程为什么不能获取锁?

能详细解释一下么,没太看明白你的问题

嗯,我是针对这个问题的提问者那个问题说的,他的问题是:

你好,想问下,cond的listen方法,看代码第一个协程获取锁后,调用wait方法等待。其他协程并不能获取到锁,也无法调用wait方法,是都被阻塞了吗

我的答案是其他协程可以获取到锁,因为 Wait 方法里面如果条件不满足会把当前 goroutine 添加到通知列表,然后执行了 c.L.Unlock ,接着 gopark 当前 goroutine,所以这时其他 goroutine 是可以获取 c.L.Lock() 的,而不是说其他 goroutine 无法调用 Wait 方法。

draveness commented 4 years ago

我的答案是其他协程可以获取到锁,因为 Wait 方法里面如果条件不满足会把当前 goroutine 添加到通知列表,然后执行了 c.L.Unlock ,接着 gopark 当前 goroutine,所以这时其他 goroutine 是可以获取 c.L.Lock() 的,而不是说其他 goroutine 无法调用 Wait 方法。

可以的,手动点赞,这节写的有点久了,上下文有点丢失...

douglarek commented 4 years ago

我的答案是其他协程可以获取到锁,因为 Wait 方法里面如果条件不满足会把当前 goroutine 添加到通知列表,然后执行了 c.L.Unlock ,接着 gopark 当前 goroutine,所以这时其他 goroutine 是可以获取 c.L.Lock() 的,而不是说其他 goroutine 无法调用 Wait 方法。

可以的,手动点赞,这节写的有点久了,上下文有点丢失...

嗯,博主写的很不错,我学到好多东西,加油~

u3uuuuuu commented 4 years ago

简单易懂,感谢

tuber commented 4 years ago

没有找到pool的身影。。

zpng commented 4 years ago

singleflight.Group 这个会抑制多久同样的请求,同一时刻还是全部生命周期的

draveness commented 4 years ago

没有找到pool的身影。。

之后会补上的

draveness commented 4 years ago

singleflight.Group 这个会抑制多久同样的请求,同一时刻还是全部生命周期的

对于这个结构体来说是永久的

dilllay commented 4 years ago

@draveness

没有找到pool的身影。。

之后会补上的

对啊,期待有pool相关的文章,很多现代框架或者高性能服务开发都会用到pool,虽然光看官方文档api并不复杂,但是也很想看看更多的细节。

KylinMountain commented 4 years ago

@douglarek

建议博主改改 sync.Cond 的例子,既然是条件变量,条件在哪呢?源码注释里的例子就很好嘛

下一次 Review 的时候我看一下改不改

这个并不一定吧;Wait 方法里面有 Unlock 和 Lock 的间隙在的,这个间隙里其他协程为什么不能获取锁?

能详细解释一下么,没太看明白你的问题

嗯,我是针对这个问题的提问者那个问题说的,他的问题是:

你好,想问下,cond的listen方法,看代码第一个协程获取锁后,调用wait方法等待。其他协程并不能获取到锁,也无法调用wait方法,是都被阻塞了吗

我的答案是其他协程可以获取到锁,因为 Wait 方法里面如果条件不满足会把当前 goroutine 添加到通知列表,然后执行了 c.L.Unlock ,接着 gopark 当前 goroutine,所以这时其他 goroutine 是可以获取 c.L.Lock() 的,而不是说其他 goroutine 无法调用 Wait 方法。

确实,乍一看不去看Wait的源码,第一感觉是其他goroutine都会被block。其实wait就是解锁自己,然后让自己陷入休眠并等待被唤醒。被唤醒,可以是broadcast也可以signal。被唤醒之后,在加锁继续执行,执行完后,在这里就是打印,然后解锁。

KylinMountain commented 4 years ago

请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}
KylinMountain commented 4 years ago

另外一个问题是那个singleflight.Group,如何做缓存呢? 我想了一下,一般go服务里的每个请求都在一个独立的goroutine中,我们显然不能在每个goroutine中都新建一个singlefilght.Group对象,因为这样一点卵用都没有,每个group里的map,wg都只在这个请求里。

如果做成全局的,那么Do里的那个g.mu.Lock 将会把这个服务上的所有的请求,都给锁死,都得等一个执行时间较长的请求返回。

如何解决这个问题呢?

KylinMountain commented 4 years ago

@zpng singleflight.Group 这个会抑制多久同样的请求,同一时刻还是全部生命周期的

要看第一个进去的请求什么时候执行成功返回结果了。

draveness commented 4 years ago

请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

~感觉 CAS 会比 Load + Store 慢一些,你可以测一下~,看 https://github.com/draveness/blog-comments/issues/151#issuecomment-667944126

如果做成全局的,那么Do里的那个g.mu.Lock 将会把这个服务上的所有的请求,都给锁死,都得等一个执行时间较长的请求返回。

不会锁死所有请求,会锁定相同的请求

tidyoux commented 4 years ago

@KylinGu 请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

注释说得很清楚:

Do guarantees that when it returns, f has finished.

This implementation would not implement that guarantee:
given two simultaneous calls, the winner of the cas would
call f, and the second would return immediately, without
waiting for the first's call to f to complete.

This is why the slow path falls back to a mutex, and why
the atomic.StoreUint32 must be delayed until after f returns.
KylinMountain commented 4 years ago

@tidyoux

@KylinGu 请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

注释说得很清楚:

Do guarantees that when it returns, f has finished.

This implementation would not implement that guarantee:
given two simultaneous calls, the winner of the cas would
call f, and the second would return immediately, without
waiting for the first's call to f to complete.

This is why the slow path falls back to a mutex, and why
the atomic.StoreUint32 must be delayed until after f returns.

@tidyoux

@KylinGu 请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

注释说得很清楚:

Do guarantees that when it returns, f has finished.

This implementation would not implement that guarantee:
given two simultaneous calls, the winner of the cas would
call f, and the second would return immediately, without
waiting for the first's call to f to complete.

This is why the slow path falls back to a mutex, and why
the atomic.StoreUint32 must be delayed until after f returns.

好吧,我还是看代码理解吧。 可是为啥不能用cas实现呢?cas也同样能实现只做一次,而且其他的调用方也不用block在那里。效率不是更高吗?

KylinMountain commented 4 years ago

@draveness

如果做成全局的,那么Do里的那个g.mu.Lock 将会把这个服务上的所有的请求,都给锁死,都得等一个执行时间较长的请求返回。

不会锁死所有请求,会锁定相同的请求

奥是,重看了一遍代码,见笑了😃,确实是锁相同的请求。感谢~

如果是全局的singleflight.Group对象,仍然存在所有请求共用一把锁的情况呢。每个请求进来,都先抢一下锁,才能执行后续的操作,后面执行完成还要加锁解锁。

所以想请教,正式生产中如何使用这种东西呢?是要把请求分类,设置不同的singleflight吗?

CHN-tingxi commented 4 years ago

每次 sync.RWMutex.RUnlock 都会将 readerWait 其减一,当它归零时该 Goroutine 就会获得写锁;

这里是不是写错了,应该是readercount减一?


2020-09-01 UPDATED: 已修复

jash16 commented 4 years ago
                    runtime_SemacquireMutex(&m.sema, queueLifo, 1)
        starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
        old = m.state
        if old&mutexStarving != 0 {
            if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
                throw("sync: inconsistent mutex state")
            }
            delta := int32(mutexLocked - 1<<mutexWaiterShift)
            if !starving || old>>mutexWaiterShift == 1 {
                delta -= mutexStarving
            }
            atomic.AddInt32(&m.state, delta)
            break
        }
        awoke = true
        iter = 0
    }

如果被runtime_SemacquireMutex唤醒了,什么情况下if old&mutexStarving != 0 这个条件又不成立了?

longyue0521 commented 3 years ago

mutexWoken — 表示从正常模式被从唤醒;这里表述不是很清楚,我对mutexWoken的理解是“用于表示当前有被唤醒/新来的goroutine竞争锁“

draveness commented 3 years ago

@longyue0521 mutexWoken — 表示从正常模式被从唤醒;这里表述不是很清楚,我对mutexWoken的理解是“用于表示当前有被唤醒/新来的goroutine竞争锁“

是认为 Mutex 可以直接从饥饿模式转到 mutexWoken 么,可以找下相关的代码么

LetLifeStop commented 3 years ago

在释放写锁的过程中,第一步应该是少写了一个变量吧

原话: 通过函数atomic.Addint32将变成正数,释放读锁

我觉得应该修改成:通过函数atomic.Addint32将readercount变成正数,释放读锁


2020-10-19 UPDATES: 已作出如下修改

-1. 调用 [`atomic.AddInt32`](https://github.com/golang/go/blob/ca7c12d4c9eb4a19ca5103ec5763537cccbcc13b/src/sync/atomic/doc.go#L93) 函数将变回正数,释放读锁;
+1. 调用 [`atomic.AddInt32`](https://github.com/golang/go/blob/ca7c12d4c9eb4a19ca5103ec5763537cccbcc13b/src/sync/atomic/doc.go#L93) 函数将 `readerCount` 变回正数,释放读锁;
mncu commented 3 years ago
// atomic_amd64
func Load64(ptr *uint64) uint64 {
    return *ptr
}

博主你好,最近在看go的源码,但看到atomic.Load这个实现感到有些疑惑,想知道:

Liangzx commented 3 years ago

Once.Do 展开了

if atomic.LoadUint32(&o.done) == 0 {
    // Outlined slow-path to allow inlining of the fast-path.
    // o.doSlow(f)
    {
        o.m.Lock()
        defer o.m.Unlock()
        if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
            f()
        }
    }
}

这里使用mutex 有锁的双重检查的味道,为什么还需要使用原子操作呢?

Liangzx commented 3 years ago

@KylinGu 请教一下大神,请问sync.Once里的Do为什么不能用以下的方式实现,看源码注释有点没看懂。 LoadUint32和StoreUint32有什么区别呢?

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

这个函数保证 Do在f() 执行后才返回,使用CAS 保证不了