• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell
您的位置:首页 > 脚本语言 >Golang > Go语言并发模型的2种编程方案

Go语言并发模型的2种编程方案

作者:junjie 字体:[增加 减小] 来源:互联网

junjie 通过本文主要向大家介绍了go语言并发,go语言,go语言环境搭建,go语言程序设计,let it go 25种语言等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

概述

我一直在找一种好的方法来解释 go 语言的并发模型:

不要通过共享内存来通信,相反,应该通过通信来共享内存

但是没有发现一个好的解释来满足我下面的需求:

1.通过一个例子来说明最初的问题
2.提供一个共享内存的解决方案
3.提供一个通过通信的解决方案

这篇文章我就从这三个方面来做出解释。

读过这篇文章后你应该会了解通过通信来共享内存的模型,以及它和通过共享内存来通信的区别,你还将看到如何分别通过这两种模型来解决访问和修改共享资源的问题。

前提

设想一下我们要访问一个银行账号:
type Account interface {
  Withdraw(uint)
  Deposit(uint)
  Balance() int
}

type Bank struct {
  account Account
}

func NewBank(account Account) *Bank {
  return &Bank{account: account}
}

func (bank *Bank) Withdraw(amount uint, actor_name string) {
  fmt.Println("[-]", amount, actor_name)
  bank.account.Withdraw(amount)
}

func (bank *Bank) Deposit(amount uint, actor_name string) {
  fmt.Println("[+]", amount, actor_name)
  bank.account.Deposit(amount)
}

func (bank *Bank) Balance() int {
  return bank.account.Balance()
}
</div>

因为 Account 是一个接口,所以我们提供一个简单的实现:

type SimpleAccount struct{
  balance int
}

func NewSimpleAccount(balance int) *SimpleAccount {
  return &SimpleAccount{balance: balance}
}

func (acc *SimpleAccount) Deposit(amount uint) {
  acc.setBalance(acc.balance + int(amount))
}

func (acc *SimpleAccount) Withdraw(amount uint) {
  if acc.balance >= int(mount) {
    acc.setBalance(acc.balance - int(amount))
  } else {
    panic("杰克穷死")
  }
}

func (acc *SimpleAccount) Balance() int {
  return acc.balance
}

func (acc *SimpleAccount) setBalance(balance int) {
  acc.add_some_latency()  //增加一个延时函数,方便演示
  acc.balance = balance
}

func (acc *SimpleAccount) add_some_latency() {
  <-time.After(time.Duration(rand.Intn(100)) * time.Millisecond)
}
</div>

你可能注意到了 balance 没有被直接修改,而是被放到了 setBalance 方法里进行修改。这样设计是为了更好的描述问题。稍后我会做出解释。

把上面所有部分弄好以后我们就可以像下面这样使用它啦:
func main() {
  balance := 80
  b := NewBank(bank.NewSimpleAccount(balance))
 
  fmt.Println("初始化余额", b.Balance())
 
  b.Withdraw(30, "马伊琍")
 
  fmt.Println("-----------------")
  fmt.Println("剩余余额", b.Balance())
}
</div>

运行上面的代码会输出:

初始化余额 80
[-] 30 马伊琍
-----------------
剩余余额 50
</div>

没错!

不错在现实生活中,一个银行账号可以有很多个附属卡,不同的附属卡都可以对同一个账号进行存取钱,所以我们来修改一下代码:
func main() {
  balance := 80
  b := NewBank(bank.NewSimpleAccount(balance))
 
  fmt.Println("初始化余额", b.Balance())
 
  done := make(chan bool)
 
  go func() { b.Withdraw(30, "马伊琍"); done <- true }()
  go func() { b.Withdraw(10, "姚笛"); done <- true }()
 
  //等待 goroutine 执行完成
  <-done
  <-done
 
  fmt.Println("-----------------")
  fmt.Println("剩余余额", b.Balance())
}
</div>

这儿两个附属卡并发的从账号里取钱,来看看输出结果:
初始化余额 80
[-] 30 马伊琍
[-] 10 姚笛
-----------------
剩余余额 70
</div>

这下把文章高兴坏了:)

结果当然是错误的,剩余余额应该是40而不是70,那么让我们看看到底哪儿出问题了。

问题

当并发访问共享资源时,无效状态有很大可能会发生。

在我们的例子中,当两个附属卡同一时刻从同一个账号取钱后,我们最后得到银行账号(即共享资源)错误的剩余余额(即无效状态)。

我们来看一下执行时候的情况:

     处理情况
             --------------
             _马伊琍_|_姚笛_
 1. 获取余额     80  |  80
 2. 取钱       -30  | -10
 3. 当前剩余     50  |  70
                ... | ...
 4. 设置余额     50  ?  70  //该先设置哪个好呢?
 5. 后设置的生效了
             --------------
 6. 剩余余额        70
</div>

上面 ... 的地方描述了我们 add_some_latency 实现的延时状况,现实世界经常发生延迟情况。所以最后的剩余余额就由最后设置余额的那个附属卡决定。

解决办法

我们通过两种方法来解决这个问题:

1.共享内存的解决方案
2.通过通信的解决方案

所有的解决方案都是简单的封装了一下 SimpleAccount 来实现保护机制。

共享内存的解决方案

又叫 “通过共享内存来通信”。

这种方案暗示了使用锁机制来预防同时访问和修改共享资源。锁告诉其它处理程序这个资源已经被一个处理程序占用了,因此别的处理程序需要排队直到当前处理程序处理完毕。

让我们来看看 LockingAccount 是怎么实现的:

type LockingAccount struct {
  lock    sync.Mutex
  account *SimpleAccount
}

//封装一下 Simp

您可能想查找下面的文章:

  • Go语言如何并发超时处理详解
  • GO语言并发编程之互斥锁、读写锁详解
  • Go语言并发模型的2种编程方案
  • Go语言并发技术详解

相关文章

  • Go语言常见哈希函数的使用
  • 在ubuntu下构建go语言开发环境的方法
  • Golang极简入门教程(四):编写第一个项目
  • Go实现比较时间大小
  • Go语言正则表达式用法实例小结【查找、匹配、替换等】
  • Go语言导出内容到Excel的方法
  • Go语言计算指定年月天数的方法
  • golang简单位运算示例
  • Go语言共享内存读写实例分析
  • Go语言中错误处理实例分析

文章分类

  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell

最近更新的内容

    • 举例详解Go语言中os库的常用函数用法
    • 在Golang中使用C语言代码实例
    • linux下通过go语言获得系统进程cpu使用情况的方法
    • Go语言里的结构体文法实例分析
    • Go语言转换所有字符串为大写或者小写的方法
    • GO语言(golang)基础知识
    • go语言实现简单http服务的方法
    • go语言按显示长度截取字符串的方法
    • go语言睡眠排序算法实例分析
    • Go语言使用MySql的方法

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有