下载APP
登录
关闭
讲堂
算法训练营
Python 进阶训练营
企业服务
极客商城
客户端下载
兑换中心
渠道合作
推荐作者
当前播放: 24 | 共享内存并发机制
00:00 / 00:00
标清
  • 标清
1.0x
  • 2.0x
  • 1.5x
  • 1.25x
  • 1.0x
  • 0.5x
网页全屏
全屏
00:00
付费课程,可试看

Go语言从入门到实战

共55讲 · 55课时,约700分钟
6120
免费
01 | Go语言课程介绍
免费
02 | 内容综述
免费
03 | Go语言简介:历史背景、发...
免费
04 | 编写第一个Go程序
免费
05 | 变量、常量以及与其他语言...
06 | 数据类型
07 | 运算符
08 | 条件和循环
09 | 数组和切片
10 | Map声明、元素访问及遍历
11 | Map与工厂模式,在Go语言...
12 | 字符串
13 | Go语言的函数
14 | 可变参数和defer
15 | 行为的定义和实现
16 | Go语言的相关接口
17 | 扩展与复用
18 | 不一样的接口类型,一样的...
19 | 编写好的错误处理
20 | panic和recover
21 | 构建可复用的模块(包)
22 | 依赖管理
23 | 协程机制
24 | 共享内存并发机制
25 | CSP并发机制
26 | 多路选择和超时
27 | channel的关闭和广播
28 | 任务的取消
29 | Context与任务取消
30 | 只运行一次
31 | 仅需任意任务完成
32 | 所有任务完成
33 | 对象池
34 | sync.pool对象缓存
35 | 单元测试
36 | Benchmark
37 | BDD
38 | 反射编程
39 | 万能程序
40 | 不安全编程
41 | 实现pipe-filter framew...
42 | 实现micro-kernel frame...
43 | 内置JSON解析
44 | easyjson
45 | HTTP服务
46 | 构建RESTful服务
47 | 性能分析工具
48 | 性能调优示例
49 | 别让性能被锁住
50 | GC友好的代码
51 | 高效字符串连接
52 | 面向错误的设计
53 | 面向恢复的设计
54 | Chaos Engineering
55 | 结束语
本节摘要
展开

精选留言(14)

  • 2019-03-29
    实验了下Lin同学的问题:解锁还没有锁的mu,运行报错!
    func TestCounterThreadSafe(t *testing.T){
        var mu sync.Mutex
        counter :=0
        for i:=0; i<5000;i++{
            go func(){
                defer func(){
                    mu.Unlock()
                }()
                panic("SomeThing Wrong!")
                mu.Lock()
                counter++
            }()
        }
        //time.Sleep(time.Microsecond*1)
        t.Logf("Counter=%v",counter)
    }

    运行出错:
    === RUN TestCounterThreadSafe
    fatal error: sync: unlock of unlocked mutex
    展开
    2
    4
  • 2019-06-05
    初学语言,用中文解释了一下,便于理解
    ```go
    func TestCounter(t *testing.T) {
        counter := 0
        for i := 0; i < 5000; i++ { // 循环出 5000 个协程程序
            go func() { //执行这个协程匿名函数给 counter 自增
                counter++
            }()
        }
        // 执行完成后得到的结果并不是 5000,这是因为这 5000 个并发协程都在抢用 counter,
        // 这样的话,整个程序是不安全的,我们需要对 counter 的共享内存做锁的保护
        time.Sleep(1 * time.Second)
        t.Logf("counter = %d", counter)
    }

    func TestCounterThreadSafe(t *testing.T) {
        var mut sync.Mutex // ⚠️ 1. 增加一个互斥锁
        counter := 0
        for i := 0; i < 5000; i++ {
            go func() {
                defer func() { // ⚠️ 3. 使用 defer 用于释放资源,此处用于解除锁
                    mut.Unlock()
                }()
                mut.Lock() // ⚠️ 2. counter 自增前加锁
                counter++
            }()
        }
        time.Sleep(1 * time.Second)
        // 加这个延时是担心程序一下就跑完了,甚至协程还没有跑完,
        // 给一个定时肯定不是万全之策,但是使用 waitGroup 就好办啦
        t.Logf("counter = %d", counter)
    }

    func TestCounterThreadSafeWaitGroup(t *testing.T) {
        var mut sync.Mutex
        var wg sync.WaitGroup // 1.声明一个等待组
        counter := 0
        for i := 0; i < 5000; i++ {
            wg.Add(1) // 3.每启动一个协程都新增加一个等待
            go func() {
                defer func() {
                    mut.Unlock()
                }()
                mut.Lock()
                counter++
                wg.Done() // 4.这个等待完成了
            }()
        }
        wg.Wait()
        // 2.这里等待上面所有的等待完成后马上走下面的程序
        // 这样就可以准确的等待协程执行了
        t.Logf("counter = %d", counter)
    }

    ```
    展开
    1
    3
  • 2019-03-27
    老师,假如mut.Lock()之前有一段代码,并且还没有执行到这里就出错了,那么defer里面的Unlock就会先执行,这样是不是会出错?

    作者回复: 好问题。你完全可以写个程序动手试试。然后,记得把结果分享给大家。

    2
    3
  • 2019-03-19
    线程机制?没有这课啊?

    作者回复: 刚才已经联系过平台了,明天平台回补上这一期

    3
  • 2019-04-01
    老师, defer 后的代码:
    defer mut.Unlock() 和 defer func() { mut.Unlock() }()
    有什么区别吗?
    2
  • 2019-03-19
    wg.done()是不是放到defer函数里更好?

    作者回复: 这个看你的应用场景。

    1
  • 2019-11-29
    为什么老师喜欢把defer函数写在最前面,看起来逻辑怪怪的

    作者回复: 1.在设计函数的时候就想清楚收尾工作,释放资源
    2.便于排错时,快速确定没有资源/锁等释放问题

  • 2019-09-25
    请教老师,waitgroup是每次add(1),done(),对应的吗?不是一接到done程序就跳到wait后执行吗?

    作者回复: 没错。对应的。不是一接到就解除wait

  • 2019-08-04
    ...
    for i := 0; i < 5000; i++ {
      // 加锁,放在协程的外面
      mutex.Lock()
      go func() {
        defer func() {
        mutex.Unlock()
        }()
        ......
      }
    }
    ...

    这样也能成功执行,结果是4999。奇怪的点是:在外面lock的,在协程内部可以unlock,也就是说协程可以unlock别人的锁???
    展开

    作者回复: 其实share memory的锁,更应该看成是共享内容上的锁,每个访问者(协程/线程)在访问共享内容前要先获取这个锁(lock),如果无法锁住就要等待释放,所以才会在每个协程里要先锁住再释放。并不是说释放别人/自己的锁

  • 2019-04-29
    func TestCounterThreadSafe(t *testing.T){
        var mu sync.Mutex
        counter :=0
        for i:=0; i<5000;i++{
            go func(){
                mu.Lock()
                defer func(){
                    mu.Unlock()
                }()
                counter++
            }()
        }
        //time.Sleep(time.Microsecond*1)
        t.Logf("Counter=%v",counter)
    }
    所以程序最好是这么写规范些,defer放在lock后
    展开
  • 2019-04-26
    老师,我想请问一下一开始线程不安全的时候为什么count会比5000少呢?

    作者回复: 因为读到脏数据(即别的协程已经更新了,但是当前协程不知道)所以,覆盖了别人的更新。

    1
  • 2019-03-26
    蔡老师您好
    多个协程并发的去修改一个变量 需要用锁或者channel
    甚至以下代码 去append一个切片 或者 给map赋值 都不行
    会出现 concurrent map writes

        for i := 0; i < 100; i++ {
            go func(i int) {
                slice = append(slice, 1)
                onlinemap[i] = i

            }(i)
        }
    那么我的问题来了:
    是不是 意味着用GO写服务器的时候 几乎所有的数据都需要加锁或者通过channel来操作了?
    比如我想创建一个TCP协议的服务器,n, err := conn.Read(buffer)
    读取过来的buffer 不管三七二十一,先放进channel再说是吗?
    展开

    作者回复: 这个要根据你的需求来看,用chan比较容易做成异步的,共享内存呢比较容易做同步的调用。当然,其实两者都可以构建同步或异步的处理。

  • 说错了。是有可能 n个 (n>0 && n<=5000) 携程没有正常执行完,主线程就退出了,看defer里的逻辑吧。

    作者回复: 很好的思考。但只可能是最后一个,因为要想执行到wg.Done()就必须得到锁,同时,只有一个携程会获得锁,必须等其他携程Unlock,而unlock又在最后一步。所以,counter的结果也不会错。

  • 老师,如果将wg.Done()按您的写法,第5000个执行mut.Unlock()的携程,有可能在主线程退出时还没有执行完。这里defer逻辑比较简单,如果是耗时的是不是可以挪过来。请老师百忙之中批评指正。
        go func() {
                defer func() {
                    mut.Unlock()
                                   wg.Done() // 移到这里
                }()
                mut.Lock()
                counter++
                wg.Done()
            }()
    展开
    1