• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

go内存模型和channel 探究

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

在多个线程对同一个变量进行了读写操作的时候,由于不同的goroutine的执行顺序不能确定,可能会为程序带来不可预测的后果。要保证程序的并发安全,需要使用锁机制。

go内存模型确定了在何种条件下一个goroutine中的read操作可以观测到另一个goroutine中的write操作。

对于在不同的goroutine中操作的数据应该用channel保护起来,或者用其他同步机制,比如sync或者sync/atomic包。

func main() {
	var m = sync.Mutex{}
	var i = 0
	m.Lock()
	go func() {
		// for {
		i = 3
		m.Unlock()

		// }
	}()
	m.Lock()
	fmt.Println(i)
	m.Unlock()
	fmt.Println("EXIST WITH CODE 0")

}

用channel实现的版本,channel保证对对通道c的写入发生在读取之前,本质上chan也是用锁来实现的。

var c = make(chan int)
var a string

func f() {
	a = "hello, world"
	<-c
}
func main() {
	go f()
	c <- 0
	print(a)
}

在上面的例子中,如果c是一个缓冲通道,则不能保证,这是由channel的底层实现决定的,从$GOROOT/src/runtime/chan.go中可以看到


type hchan struct {
	qcount   uint           // 队列中的有效数据,等于队列长度代表满
	dataqsiz uint           // 队列的总长度
	buf      unsafe.Pointer // 指向堆中的指针
	elemsize uint16
	closed   uint32
	elemtype *_type // element type,这个变量决定了单位内存大小
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

下面摘自 Gopher 2017大会上Kavya的PPT

一个Buffer channel 底层是一个hchan结构体,用make创建一个channel的时候,实际上返回的是指向堆中的一个buffer的指针,数据存储在 环形队列 中,等待写入或读出数据的goroutine放在两个队列 recvq 和 sendq 中,还有一个lock来保证并发安全。
有这样两个性质。

  • FIFO先进先出(队列的性质)
  • 并发安全,(读写都加了排他锁)
  • 可以让goroutine 挂起和唤醒

当一个goroutine向channel中写入数据的时候,

  • 先给通道加上锁
  • 然后数据copy入队列,sendx +1
  • 再将这个锁释放。

同样的,一个goroutine从channel中读取数据的时候,

  • 为通道加上锁
  • 然后从队列中copy出数据, recvx +1
  • 将这个锁释放

向一个已经关闭的channel发送数据会引发panic,关闭一个已经关闭的channel也会引发panic。

从一个已经关闭的channel中读取数据不会引发Panic,会读出一个zero value,为了避免死循环,在读出channel时应该判断channel是否关闭。

	val, ok := <- ch
	if !ok{
	log.Println("channel closed")
	return
	}
	fmt.Println(val)

当所有的goroutine都停止或者处于阻塞的时候,主线程还未退出,会引发死锁。
另外,go语言采用自带的调度器来暂停、唤醒goroutine,而系统级别的OS线程始终是运行的。这也是goroutine比系统级线程更加轻量的原因。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
go语言入门之环境的搭建发布时间:2022-07-10
下一篇:
GO_06:GO语言基础之struct发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap