草庐IT

Go 快速入门指南 - 互斥锁和定时器

洋芋土豆 2023-03-28 原文

互斥锁

对于任一共享资源,同一时间保证只有一个操作者,这种方法称为 互斥机制

关键字 Mutex 表示互斥锁类型,它的 Lock 方法用于获取锁,Unlock 方法用于释放锁。在 Lock 和 Unlock 之间的代码,可以读取和修改共享资源,这部分区域称为 临界区

错误的并发操作

先来看一个错误的示例。

在 Map 小节中讲到, Map 不是并发安全的, 也就是说,如果在多个线程中,同时对一个 Map 进行读写,会报错。现在来验证一下, 通过启动 100 个 goroutine 来模拟并发调用,每个 goroutine 都对 Map 的 key 进行设置。

package main

import "sync"

func main() {
    m := make(map[int]bool)

    var wg sync.WaitGroup

    for j := 0; j < 100; j++ {
        wg.Add(1)

        go func(key int) {
            defer func() {
                wg.Done()
            }()

            m[key] = true // 对 Map 进行并发写入
        }(j)
    }

    wg.Wait()
}

// $ go run main.go
// 输出如下,报错信息
/**
  fatal error: concurrent map writes
  fatal error: concurrent map writes

  goroutine 104 [running]:
  main.main.func1(0x0?)
          /home/codes/Go-examples-for-beginners/main.go:18 +0x66
  created by main.main
          /home/codes/Go-examples-for-beginners/main.go:13 +0x45

  goroutine 1 [semacquire]:
  sync.runtime_Semacquire(0xc0000112c0?)
          /usr/local/go/src/runtime/sema.go:62 +0x25
  sync.(*WaitGroup).Wait(0x60?)
          /usr/local/go/src/sync/waitgroup.go:139 +0x52
  main.main()
          /home/codes/Go-examples-for-beginners/main.go:22 +0x105

  ...
  ...
  ...
*/

通过输出信息 fatal error: concurrent map writes 可以看到,并发写入 Map 确实会报错。

正确的并发操作

Map 并发写入如何正确地实现呢?

一种简单的方案是在并发临界区域 (也就是设置 Map key 的地方) 进行加互斥锁操作, 互斥锁保证了同一时刻 只有一个 goroutine 获得锁,其他 goroutine 全部处于等待状态,这样就把并发写入变成了串行写入, 从而消除了报错问题。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    m := make(map[int]bool)

    var wg sync.WaitGroup

    for j := 0; j < 100; j++ {
        wg.Add(1)

        go func(key int) {
            defer func() {
                wg.Done()
            }()

            mu.Lock()     // 写入前加锁
            m[key] = true // 对 Map 进行并发写入
            mu.Unlock()   // 写入完成解锁
        }(j)
    }

    wg.Wait()

    fmt.Printf("Map size = %d\n", len(m))
}

// $ go run main.go
// 输出如下
/**
  Map size = 100
*/

超时控制

利用 channel (通道) 和 time.After() 方法实现超时控制。

例子

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan bool)

    go func() {
        defer func() {
            ch <- true
        }()

        time.Sleep(2 * time.Second) // 模拟超时操作
    }()

    select {
    case <-ch:
        fmt.Println("ok")
    case <-time.After(time.Second):
        fmt.Println("timeout!")
    }
}

// $ go run main.go
// 输出如下
/**
  timeout!
*/

定时器

调用 time.NewTicker 方法即可。

例子

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    done := make(chan bool)
    go func() {
        time.Sleep(5 * time.Second) // 模拟耗时操作
        done <- true
    }()

    for {
        select {
        case <-done:
            fmt.Println("Done!")
            return
        case <-ticker.C:
            fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
        }
    }
}

// $ go run main.go
// 输出如下,你的输出可能和这里的不一样
/**
  2021-01-03 15:40:21
  2021-01-03 15:40:22
  2021-01-03 15:40:23
  2021-01-03 15:40:24
  2021-01-03 15:40:25
  Done!
*/

扩展阅读

  1. 1. 互斥锁 - 维基百科 (https://zh.wikipedia.org/wiki/互斥锁)

  2. 2. 临界区 - 百度百科 (https://baike.baidu.com/item/临界区/8942134)

联系我

有关Go 快速入门指南 - 互斥锁和定时器的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  3. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  4. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  5. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  6. ruby - 如何以表格格式快速打印 Ruby 哈希值? - 2

    有没有办法快速将表格格式的ruby​​哈希打印到文件中?如:keyAkeyBkeyC...1232343451253474456...其中散列的值是不同大小的数组。还是使用双循环是唯一的方法?谢谢 最佳答案 试试我写的这个gem(在表中打印散列、ruby对象、ActiveRecord对象):http://github.com/arches/table_print 关于ruby-如何以表格格式快速打印Ruby哈希值?,我们在StackOverflow上找到一个类似的问题:

  7. ruby-on-rails - Rails 中的类实例变量应该在互斥体中设置吗? - 2

    假设我的Rails项目中有一个设置实例变量的Ruby类。classSomethingdefself.objects@objects||=begin#somelogicthatbuildsanarray,whichisultimatelystoredin@objectsendendend是否可以多次设置@objects?是否有可能在一个请求期间,在上面的begin/end之间执行代码时,可以在第二个请求期间调用此方法?我想这实际上归结为Rails服务器实例如何fork的问题。我应该改用Mutex还是线程同步?例如:classSomethingdefself.objectsreturn@o

  8. Ruby 和指南针路径与 yeoman 项目 - 2

    我安装了ruby​​、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom

  9. ruby-on-rails - Textmate 'Go to symbol' 相当于 Vim - 2

    在Railcasts上,我注意到一个非常有趣的功能“转到符号”窗口。它像Command-T一样工作,但显示当前文件中可用的类和方法。如何在vim中获取它? 最佳答案 尝试:helptags有各种程序和脚本可以生成标记文件。此外,标记文件格式非常简单,因此很容易将sed(1)或类似的脚本组合在一起,无论您使用何种语言,它们都可以生成标记文件。轻松获取标记文件(除了下载生成器之外)的关键在于格式化样式而不是实际解析语法。 关于ruby-on-rails-Textmate'Gotosymbol

  10. 电脑启动后显示器黑屏怎么办?排查下面4个问题,快速解决 - 2

    电脑启动出现显示器黑屏是一个相当常见的问题。如果您遇到了这个问题,不要惊慌,因为它有很多可能的原因,可以采取一些简单的措施来解决它。在本文中,小编将介绍下面4种常见的电脑启动后显示器黑屏的原因,排查这些原因,快速解决! 演示机型:联想Ideapad700-15ISK-ISE系统版本:Windows10一、显示器问题如果出现电脑启动后显示器黑屏的情况。那么首先您需要检查一下显示器是否正常工作。您可以通过更换另一个显示器或将当前显示器连接到另一台计算机来检查显示器是否存在问题。如果问题仍然存在,那么您可以排除显示器故障的可能性。 二、显卡问题如果您的电脑配备了独立显卡,那么显卡故障也可能是导致电脑

随机推荐