分享
  1. 首页
  2. 文章

golang 线程与通道

niechaoya · · 27074 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

首先我们来看线程,在golang里面也叫goroutine

在读这篇文章之前,我们需要了解一下并发与并行。golang的线程是一种并发机制,而不是并行。它们之间的区别大家可以上网搜一下,网上有很多的介绍。

下面我们先来看一个例子吧

import(

"fmt"

)

funcmain(){
gofmt.Println("1")
fmt.Println("2")
}

在golang里面,使用go这个关键字,后面再跟上一个函数就可以创建一个线程。后面的这个函数可以是已经写好的函数,也可以是一个匿名函数

funcmain(){

vari=3

gofunc(a int){
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2")

}
上面的代码就创建了一个匿名函数,并且还传入了一个参数i,下面括号里的i是实参,a是形参。

那么上面的代码能按照我们预想的打印1、2、3吗?告诉你们吧,不能,程序只能打印出2。下面我把正确的代码贴出来吧

import(

"fmt"
"time"
)
funcmain(){
vari=3
gofunc(a int){
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2")
 time.Sleep(1*time.Second)
}
我只是在最后加了一行让主线程休眠一秒的代码,程序就会依次打印出2、3、1。
那为什么会这样呢?因为程序会优先执行主线程,主线程执行完成后,程序会立即退出,没有多余的时间去执行子线程。如果在程序的最后让主线程休眠1秒钟,那程序就会有足够的时间去执行子线程。


线程先讲到这里,下面我们来看看通道吧。

通道又叫channel,顾名思义,channel的作用就是在多线程之间传递数据的。

创建无缓冲channel

chreadandwrite :=make(chan int)

chonlyread:=make(<-chan int)//创建只读channel
chonlywrite:=make(chan<- int)//创建只写channel

下面我们来看一个例子:

ch:=make(chan int)

 ch<-1
 gofunc(){
<-ch
fmt.Println("1")
}()
fmt.Println("2") 
这段代码执行时会出现一个错误:fatal error: all goroutines are asleep - deadlock!


这个错误的意思是说线程陷入了死锁,程序无法继续往下执行。那么造成这种错误的原因是什么呢?

我们创建了一个无缓冲的channel,然后给这个channel赋值了,程序就是在赋值完成后陷入了死锁。因为我们的channel是无缓冲的,即同步的,赋值完成后来不及读取channel,程序就已经阻塞了。这里介绍一个非常重要的概念:channel的机制是先进先出,如果你给channel赋值了,那么必须要读取它的值,不然就会造成阻塞,当然这个只对无缓冲的channel有效。对于有缓冲的channel,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

对于上面的例子有两种解决方案:

1、给channel增加缓冲区,然后在程序的最后让主线程休眠一秒,代码如下:

ch:=make(chan int,1)

ch<-1
gofunc(){
v:=<-ch
fmt.Println(v)
}()
time.Sleep(1*time.Second)
fmt.Println("2")
这样的话程序就会依次打印出1、2

2、把ch<-1这一行代码放到子线程代码的后面,代码如下:

ch:=make(chan int)

gofunc(){
v:=<-ch
fmt.Println(v)
}()
ch<-1
fmt.Println("2")

这里就不用让主线程休眠了,因为channel在主线程中被赋值后,主线程就会阻塞,直到channel的值在子线程中被取出。


最后我们看一个生产者和消费者的例子:

import(

"fmt"
"time"
)
funcproduce(pchan<- int){
fori:=0;i<10;i++{
p<-i
fmt.Println("send:",i)
}
}
funcconsumer(c<-chan int){
fori:=0;i<10;i++{
v:=<-c
fmt.Println("receive:",v)
}
}
funcmain(){
ch:=make(chan int)
goproduce(ch)
goconsumer(ch)
time.Sleep(1*time.Second)
}
在这段代码中,因为channel是没有缓冲的,所以当生产者给channel赋值后,生产者这个线程会阻塞,直到消费者线程将channel中的数据取出。消费者第一次将数据取出后,进行下一次循环时,消费者的线程也会阻塞,因为生产者还没有将数据存入,这时程序会去执行生产者的线程。程序就这样在消费者和生产者两个线程间不断切换,直到循环结束。


下面我们再看一个带缓冲的例子:

import(

"fmt"
"time"
)
funcproduce(pchan<- int){
fori:=0;i<10;i++{
p<-i
fmt.Println("send:",i)
}
}
funcconsumer(c<-chan int){
fori:=0;i<10;i++{
v:=<-c
fmt.Println("receive:",v)
}
}
funcmain(){
ch:=make(chan int,10)
goproduce(ch)
goconsumer(ch)
time.Sleep(1*time.Second)
}

在这个程序中,缓冲区可以存储10个int类型的整数,在执行生产者线程的时候,线程就不会阻塞,一次性将10个整数存入channel,在读取的时候,也是一次性读取。



有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:niechaoya

查看原文:golang 线程与通道

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
27074 次点击
5 回复 | 直到 2018年09月05日 21:46:02
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏