谈谈goroutine

背景知识:

  1. 关于线程和进程的关系:简单理解进程是用来管理资源的,而线程是利用cpu执行代码(指令),一个应用程序至少有一个进程,一个进程至少有一个线程。线程是不具备资源的,但是它可以访问所属进程的资源。
  2. 关于协程和线程的主要区别:线程是抢占式的,任何时刻都有可能被操作系统切换,它是没有控制权的,换句话说一个操作可能执行到一半被中断,cpu去执行其他线程。协程是非抢占式的,由自己主动交出控制权。

goroutine(非抢占式)不是真正意义上的协程,它与传统意义上的协程不同(传统意义上的协程控制权是由自己交出的),而go的调度器会在合适的点进行控制权切换:( 需要注意:只是参考,不能保证一定切换,不能保证其他地方一定不切换

1. I/O(例如:fmt包的操作) , select
2. channel
3. 等待锁
4. 函数的调用(这是一个切换的机会,到底切不切换由调度器决定)
5. runtime Gosched() 
6. 阻塞,例如time.Sleep()

在传统逻辑中,开发者一般要维护线程池中线程与CPU核心数量间的对应关系。同样go语言也是如此:

runtime.GOMAXPROCS(逻辑CPU数量)
//cpu的逻辑数量如何获取
runtime.NumCPU()

在go语言1.5版本之前使用单线程模式的,但是在1.5版本之后默认会执行:runtime.GOMAXPROCS(runtime.NumCPU())以达到最大程度的利用CPU。接下来对比下面的两个例子:

var num int

//例子一
func main() {
	runtime.GOMAXPROCS(1)
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			num ++
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(num)
}

//例子二
func main() {
	runtime.GOMAXPROCS(2)
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			num ++
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(num)
}

通过运行程序我们可以发现,例子一的结果一直是1000,这是因为“ runtime.GOMAXPROCS(1) ”保证是单线程,所有的goroutine都是在一个线程中串行的,因此不会有访问冲突的问题,但是例子二结果就不一定是1000了,因为开启了两个线程,goroutine就会分布在多个线程中运行,那么就会有访问冲突的问题,解决方案可以使用锁:

func main() {
	runtime.GOMAXPROCS(2)
	var wg sync.WaitGroup
	var lock sync.Mutex
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			lock.Lock()
			defer lock.Unlock()
			num ++
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(num)
}

所以goroutine所谓“无锁”的优点只在单线程下有效,如果$GOMAXPROCS > 1并且协程间需要通信,那么就需要引入锁机制来保证数据访问不冲突。

扩展阅读:阅读一 和 阅读二

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值