打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
互斥锁,Go状态协程

互斥锁

代码实例

// 在前面的例子中,我们看到了如何使用原子操作来管理简单的计数器。// 对于更加复杂的情况,我们可以使用一个_[互斥锁](http://zh.wikipedia.org/wiki/%E4%BA%92%E6%96%A5%E9%94%81)_// 来在 Go 协程间安全的访问数据。package mainimport (    "fmt"    "math/rand"    "runtime"    "sync"    "sync/atomic"    "time")func main() {    // 在我们的例子中,`state` 是一个 map。    var state = make(map[int]int)    // 这里的 `mutex` 将同步对 `state` 的访问。    var mutex = &sync.Mutex{}    // we'll see later, `ops` will count how many    // operations we perform against the state.    // 为了比较基于互斥锁的处理方式和我们后面将要看到的其他    // 方式,`ops` 将记录我们对 state 的操作次数。    var ops int64 = 0    // 这里我们运行 100 个 Go 协程来重复读取 state。    for r := 0; r < 100; r++ {        go func() {            total := 0            for {                // 每次循环读取,我们使用一个键来进行访问,                // `Lock()` 这个 `mutex` 来确保对 `state` 的                // 独占访问,读取选定的键的值,`Unlock()` 这个                // mutex,并且 `ops` 值加 1。                key := rand.Intn(5)                mutex.Lock()                total += state[key]                mutex.Unlock()                atomic.AddInt64(&ops, 1)                // 为了确保这个 Go 协程不会再调度中饿死,我们                // 在每次操作后明确的使用 `runtime.Gosched()`                // 进行释放。这个释放一般是自动处理的,像例如                // 每个通道操作后或者 `time.Sleep` 的阻塞调用后                // 相似,但是在这个例子中我们需要手动的处理。                runtime.Gosched()            }        }()    }    // 同样的,我们运行 10 个 Go 协程来模拟写入操作,使用    // 和读取相同的模式。    for w := 0; w < 10; w++ {        go func() {            for {                key := rand.Intn(5)                val := rand.Intn(100)                mutex.Lock()                state[key] = val                mutex.Unlock()                atomic.AddInt64(&ops, 1)                runtime.Gosched()            }        }()    }    // 让这 10 个 Go 协程对 `state` 和 `mutex` 的操作    // 运行 1 s。    time.Sleep(time.Second)    // 获取并输出最终的操作计数。    opsFinal := atomic.LoadInt64(&ops)    fmt.Println("ops:", opsFinal)    // 对 `state` 使用一个最终的锁,显示它是如何结束的。    mutex.Lock()    fmt.Println("state:", state)    mutex.Unlock()}

运行程序

# 运行这个程序,显示我们对已进行了同步的 `state` 执行了# 3,500,000 次操作。$ go run mutexes.goops: 3598302state: map[1:38 4:98 2:23 3:85 0:44]# 接下来我们将看一下只使用 Go 协程和通道是如何实现# 相同的状态控制任务的。

Go状态协程

代码实例

// 在前面的例子中,我们用互斥锁进行了明确的锁定来让共享的// state 跨多个 Go 协程同步访问。另一个选择是使用内置的 Go// 协程和通道的的同步特性来达到同样的效果。这个基于通道的方// 法和 Go 通过通信以及    每个 Go 协程间通过通讯来共享内存,确// 保每块数据有单独的 Go 协程所有的思路是一致的。package mainimport (    "fmt"    "math/rand"    "sync/atomic"    "time")// 在这个例子中,state 将被一个单独的 Go 协程拥有。这就// 能够保证数据在并行读取时不会混乱。为了对 state 进行// 读取或者写入,其他的 Go 协程将发送一条数据到拥有的 Go// 协程中,然后接收对应的回复。结构体 `readOp` 和 `writeOp`// 封装这些请求,并且是拥有 Go 协程响应的一个方式。type readOp struct {    key  int    resp chan int}type writeOp struct {    key  int    val  int    resp chan bool}func main() {    // 和前面一样,我们将计算我们执行操作的次数。    var ops int64    // `reads` 和 `writes` 通道分别将被其他 Go 协程用来发    // 布读和写请求。    reads := make(chan *readOp)    writes := make(chan *writeOp)    // 这个就是拥有 `state` 的那个 Go 协程,和前面例子中的    // map一样,不过这里是被这个状态协程私有的。这个 Go 协程    // 反复响应到达的请求。先响应到达的请求,然后返回一个值到    // 响应通道 `resp` 来表示操作成功(或者是 `reads` 中请求的值)    go func() {        var state = make(map[int]int)        for {            select {            case read := <-reads:                read.resp <- state[read.key]            case write := <-writes:                state[write.key] = write.val                write.resp <- true            }        }    }()    // 启动 100 个 Go 协程通过 `reads` 通道发起对 state 所有者    // Go 协程的读取请求。每个读取请求需要构造一个 `readOp`,    // 发送它到 `reads` 通道中,并通过给定的 `resp` 通道接收    // 结果。    for r := 0; r < 100; r++ {        go func() {            for {                read := &readOp{                    key:  rand.Intn(5),                    resp: make(chan int)}                reads <- read                <-read.resp                atomic.AddInt64(&ops, 1)            }        }()    }    // 用相同的方法启动 10 个写操作。    for w := 0; w < 10; w++ {        go func() {            for {                write := &writeOp{                    key:  rand.Intn(5),                    val:  rand.Intn(100),                    resp: make(chan bool)}                writes <- write                <-write.resp                atomic.AddInt64(&ops, 1)            }        }()    }    // 让 Go 协程们跑 1s。    time.Sleep(time.Second)    // 最后,获取并报告 `ops` 值。    opsFinal := atomic.LoadInt64(&ops)    fmt.Println("ops:", opsFinal)}

运行程序

# 运行这个程序显示这个基于 Go 协程的转台管理的例子达到# 了每秒大约 800,000 次操作。$ go run stateful-goroutines.goops: 807434# 在这个特殊的例子中,基于 Go 协程的比基于互斥锁的稍复杂。# 这在某些例子中会有用,例如,在你有其他通道包含其中或者当你# 管理多个这样的互斥锁容易出错的时候。你应该使用最自然# 的方法,特别是关于程序正确性的时候。
动手实践是学习 IT 技术最有效的方式! 开始实验
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
golang随机数生成踩过的坑记录一下
分享一个超级百度网盘下载工具,go语言全新编写(附整个项目)
使用golang结合goquery抓取数据的爬虫
Golang1.7动态库的使用,C和Golang的动态库以及使用
Go 每日一库之 bytebufferpool
微服务过载保护原理与实战
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服