理解Go里面的goroutine和channel。(一)

首先我们了解下进程和线程

进程是内存资源管理和cpu调度的执行单元。为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程里存在

多个线程,这多个线程还是共享同一片内存空间,但CPU调度的最小单元变成了线程资源分配给进程,同一进程的所有线程共享该进程的所有资源。 处理机分给线程,即真正在处理机上运行的是线程。

比方说:如果把上课的过程比作进程,那么每个学生就是一个线程,他们共享教室,即线程共享进程的内存空间。每一个时刻,只能一个学生问老师问题,老师回答完毕,轮到下一个。即线程在一个时间片内占有cpu。

携程又是什么东西,和线程有什么差异呢?

携程:

可以看作是轻量级的线程。但与线程不同的是,线程的切换需要由操作系统来控制,而携程的切换是有用户控制的。

go的goroutine

goroutine的本质是线程,但有两点不同:

1.goroutine可以实现并行,也就是说,多个协程可以在同个处理器同时跑。而协程同一时刻只能在一个处理器上跑(把宿主语音

想象成单线程就好了)。

2.goroutine之间的通信是通过channel,而协程的通信是通过yieud和resume()操作

在GO里面实现goroutine非常简单,只需要在函数的调用前面加关键字go即可,

go funcName()

下面的例子演示,启动10个goroutinues分别打印索引

package main
import (
"fmt"
"time"	
)

func main() {
	for i:=1;i<10;i++ {
		go func(i int) {
			fmt.Println(i)
		}(i)
	}
	//暂停一会,保证打印全部结束
	time.Sleep(1e9)
}

在分析goroutine执行的随机性和并发性,把goroutine看作是java的守护线程是完全可以的。上面的例子中,启动了10个goroutinues,再加上main函数的主goroutine,总共有11个goroutinues。由于goroutine类似于“守护线程”,如果主goroutine不等待片刻,可能程序就没有输出打印了。上面的例子输出如下:(输出的索引完全随机的)

 

go的channel

java里面并发主要是靠锁住临界资源(共享内存)来保证同步的。而在channel则是goroutinues之间进行通信的利器。

channel 可以形象比喻为工厂里的传送带,一头的生产者goroutinue往传输带放东西,另一头的消费者goroutinue则

从传输带取东西。channel实际上是一个有类型的消息队列,遵循先进先出的特点。

1.channel的操作符号

ch<- ele 表示ele被发送给channel ch:

ele2 <- ch 表示从channel ch取一个值,然后赋给ele2

2.阻塞式channel

channel 默认是没有缓冲区的,也就是说,通信是组赛的。send操作必须等到有消费者accpt才算完成。

举个例子:

package main
import "fmt"

func main() {
	ch1 := make(chan int)
	go pump(ch1) // pump hangs
	fmt.Println(<-ch1) // prints only 0
}

func pump(ch chan int) {
	for i:= 0; ; i++ {
		ch <- i
	}
}

上面代码pump()里的channel在接受到第一个元素后就被阻塞了,直到主goroutinue拿走了数据。最终channel阻塞在接受第二个元素,程序只打印 0

3.带有buff的channel

没有buff的channel只能容纳一个元素,而带有buff的channel则可以非阻塞容纳N个元素。发送数据到buffed channel不会被阻塞,除非channel已满;同样的,从buffed取数据也不会被阻塞,除非channel空了。这就有点像java的ConcurrentLinkedQueue。

补充:我们使用Go语言,基本上是因为他原生支持的高并发:Goroutine 和 Channel;
Go 的并发属于 CSP 并发模型的一种实现;
CSP 并发模型的核心概念是:“不要通过共享内存来通信,而应该通过通信来共享内存”。

goroutine和channel的应用

结合goroutine和channel,可以模拟出java处理并发的情况的若干场景

1.实现future

package main
import "fmt"
import "time"

func main() {
	future := heavyCalculation()
	fmt.Println(<-future)
}

func heavyCalculation() (chan int) {
	
	future := make(chan int)
	go func() {
		//模拟耗时计算
		time.Sleep(1e9)
		future <- 666
	}()
	
	return future
}

2.实现CountDownLatch

package main
import "fmt"

func main() {
	nTask := 5
	ch := make(chan int)
	for i:=1;i<=nTask;i++ {
		go doTask(ch)
	}
	for i:=1;i<=nTask;i++ {
		<-ch
	}
	fmt.Println("finished all tasks")
}

func doTask(ch chan<- int) {
	//doSth...
	ch<- 0
}

3.并发访问对象

HashTable是线程安全的,意味着多条线程同时操作hashtable对象是不会引起状态不一致的。查看HashTable源码就知道,

几乎全部方法都添加了synchronized关键字,例如:put(),remove()操作。在go里,我们可以在对象内部保存一个函数类型

的channel,涉及对象状态的操作都放入到channel中,对象初始化的时候开启一条goroutine,不停地执行匿名函数。

package main
import (
"fmt"
"strconv"
"time"	
)

type Person struct {
	Name string
	salary float64
	chF chan func()
}
func NewPerson(name string, salary float64) *Person {
	p := &Person{name, salary, make(chan func())}
	go p.backend()
	return p
}
func (p *Person) backend() {
	for f := range p.chF {
		f()
	}
}

func (p *Person) AddSalary(sal float64) {
	p.chF <- func() { p.salary += sal }  // (ThreadSafe)
	
	// p.salary += sal (NotThreadSafe)
}

func (p *Person) ReduceSalary(sal float64) {
	p.chF <- func() { p.salary -= sal }  // (ThreadSafe)
	
	// p.salary -= sal (NotThreadSafe)
}

func (p *Person) Salary() float64 {
	fChan := make(chan float64)
	p.chF <- func() { fChan <- p.salary }
	return <-fChan
}
func (p *Person) String() string {
	return p.Name + " - salary is: " + 
	strconv.FormatFloat(p.Salary(), 'f', 2, 64)
}

func main() {
	p := NewPerson("Kingston", 8888.8)
	fmt.Println(p)
	for i:=1;i<=500;i++ {
		go func() {
			p.AddSalary(1);
		}()
	}
	for i:=1;i<=500;i++ {
		go func() {
			p.ReduceSalary(1);
		}()
	}
	time.Sleep(3e9)
	fmt.Println("After changed:")
	fmt.Println(p)
}

4.生产者消费者模式

每次涉及到并发情景,都喜欢用生产者和消费者模式,因为它太经典了。

2个面包师同时生产面包,5个顾客同时取面包(尽管以下例子的打印不能说明并发的真实情况,因为channel的操作和打印的组合不是原子操作,但不影响程序的逻辑)

package main
import (
"fmt"
"time"
)


func main() {
	bread := make(chan int,3)
	for i:=1;i<=2;i++ {
		go produce(bread)
	}
	for i:=1;i<=5;i++ {
		go consume(bread)
	}
	time.Sleep(1e9)
}

func produce(ch chan<- int) {
	for {
		ch <- 1
		fmt.Println("produce bread")
		time.Sleep(100 * time.Millisecond)
	}
}

func consume(ch <-chan int) {
	for {
		<-ch
		fmt.Println("take bread")
		time.Sleep(200 * time.Millisecond)
	}
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值