前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >go语言学习-并发编程

go语言学习-并发编程

作者头像
solate
发布2019-07-22 16:54:25
5710
发布2019-07-22 16:54:25
举报
文章被收录于专栏:solate 杂货铺solate 杂货铺

goroutine

  1. 一个goroutine仅需4~5KB的栈内存(按需增减),不线程栈相比也少得多。这让“同时运行”成千上万个并发任务成为可能。(注意:不要滥用!)
  2. 通过基于OS线程的多路复用技术来实现更灵活的调度和管理,这也为并行执行提供了底层支持。 而协程(coroutine),通常只是通过在同一线程上的切换调度(yield/resume)来实现幵发执行

并发

什么是并发
什么是并发
并行
并行
协程
协程

channel

代码语言:javascript
复制
var ch1 chan = make(chan int) // int类型的非缓冲通道 
ch2 := make(chan int, 5) // int类型的缓冲通道
  • 非缓存通道:接收数据一定会在发送数据之前发生。
  • 缓存通道:发送数据一定会在接收数据之前发生。
  • 启动goroutine的go语句一定会在这个goroutine开始执行之前执行。

单向channel

可将 channel 指定为单向通道。比如:

  • "<-chan string"仅能从通道接收字符串,
  • "chan<- string"仅能向通道发送字符串。
代码语言:javascript
复制
func receive(over chan<- bool) { 
    over <- true
}

channel select

  • 从多个不同的并发执行的goroutines获取值,则可以用select来协助完成。
  • select可以监听多个channel的输入数据,一个channel对应一个case
  • 当任何被监听的channel中都没有的数据的时候,select语句块会阻塞
  • select可以有一个default子句。当任何被监听的channel中都没有的数据的时候,default子句将会被执行
  • 不switch不同的是,select语句块中丌能出现fallthrough

select 行为

  1. 当所有的被监听channel中都无数据时,则select会一直等到其中一个有数据为止。
  2. 当多个被监听channel中都有数据时,则select会随机选择一个case 执行。
  3. 当所有的被监听channel中都无数据,且default子句存在时,则 default子句会被执行。
  4. 如果想持续的监听多个channel的话需要用for语句协助。

channel 与 time 包结合使用

1.After函数:起到定时器的作用,指定的纳秒后会向返回的channel中放入一个当前时间(time.Time)的实例。

代码语言:javascript
复制
//time.After()  timeout

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	go func() {
		for i := 0; i < 10 ; i++  {
			ch1 <- i
			time.Sleep(1 * time.Second)
		}
	}()

	timeout := time.After(5 * time.Second)
	overTag := make(chan bool)
	go func() {
		for  {
			select {
			case v1, ok := <- ch1:
				if !ok {
					overTag <- true
					break
				}
				fmt.Printf("%s:%d \n", "CH1", v1)
			case <- timeout:
				fmt.Println("Timeout.")
				overTag <- true
			}
		}
	}()

	<- overTag
	fmt.Println("End.")
}

2.Tick函数:起到循环定时器的作用,每过指定的纳秒后都会向返回的channel中放入一个当前时间(time.Time)的实例

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	go func() {
		for i := 0; i < 5; i++ {
			ch1 <- i
			//time.Sleep(1 * time.Second)
		}
		close(ch1)
	}()
        //每1秒会将一个"当前时间"数据放入通道 tick中。(1秒取一次)
	tick := time.Tick(1 * time.Second) 
	overTag := make(chan bool)
	go func() {
		for {
			select {
			case <-tick:
				v, ok := <-ch1
				if !ok {
					overTag <- true
					fmt.Println("Closed channel.")
					break
				}
				fmt.Printf("%s: %d\n", "CH1", v)
			}
		}
	}()
	<-overTag
	fmt.Println("End.")
}

3.Ticker结构:循环定时器。Tick函数就是包装它来完成功能的。该定时器可以被中止。

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	//NewXxx(...)相当于Java中的工厂方法, 在Go中属于实例化 struct的惯用法,使用相当广泛
	ticker := time.NewTicker(1 * time.Second)
	go func() {
		for {

			select {
			case <-ticker.C:
					select {//select语句块也可以用在发送端,这 相当于每次随机选择一个case执行。
					case ch1 <- 1:
					case ch1 <- 2:
					case ch1 <- 3:
					}
            //每次等待ticker的“事件”到来,限时2秒, 若超时则关闭ch1并结束该goroutine。
			case <-time.After(2 * time.Second): 
				fmt.Println("Time out. Stopped ticker.")
				close(ch1)
				return
			}

		}
	}()
	overTag := make(chan bool)
	go func() {
		for {
			select {
			case v, ok := <-ch1:
				if !ok {
					fmt.Println("Closed channel.")
					overTag <- true
					break
				}
				fmt.Printf("%s: %d\n", "CH1", v)
			}
		}
	}()
	time.Sleep(5 * time.Second)
	fmt.Println("Stop ticker.")
	ticker.Stop()//5s停止
	<-overTag
	fmt.Println("End.")
}

runtime 包在并发中的使用。

默认情况下,调度器仅使用单线程,要想发挥多核处理器的并行处理能力,必须调用 runtime.GOMAXPROCS(n)来设置可并发的线程数,也可以通过设置环境变量 GOMAXPROCS 达到相同的目的

Gosched()让当前正在执行的 goroutine 放弃 CPU 执行权限,调度器安排其它正在等待的线程运行

runtime.Goexit()函数用于终止当前 goroutine,但 defer 函数将会被继续调用。

chan 使用 生产者/消费者

  • 可以使用 range 从 channel 中取数据,直到遇到 channel close 才停止。
  • 可以将 channel 指定为单向通信。比如”<-chan int”仅能接收,”chan<-int”仅能发送
  • 向带缓冲的channel发送数据时,只有缓冲区满时,发送操作才会被阻塞。 当缓冲区空时,接收操作才会阻塞
  • 如果有多个 channel 需要监听,可以考虑用 select,随机处理一个可用的 channel
代码语言:javascript
复制
package main

import "fmt"

func producer(c chan<- int) {
	defer close(c)
	for i := 0; i < 10 ; i++  {
		c <- i //阻塞,直到数据被消费者取走后才能发送下一条数据
	}
}

func consumer(c <-chan int, f chan<- int)  {
	for {
		if v, ok := <-c; ok {
			fmt.Println(v) //阻塞,直到生产者放入数据后继续取数据
		} else {
			break
		}

	}
	/* 或者
	for v := range c {
		fmt.Println(v)
	}
	*/
	f <- 1 //向 F 发一个数据,告诉 main 数据已接收完成
}

func main() {
	buf := make(chan int)
	flg := make(chan int)
	go producer(buf)
	go consumer(buf, flg)
	<-flg //等待数据接收完成
}

进程同步

互斥锁:在读文件的时候,不能进行写入的操作,在写入时,不能进行读的操作。这就 是互斥锁。

读写锁:在读文件的时候,不能充许两个线程,同时读写,但如果两个线程同时读是没有问题的。只要在读的时候不要有写的线程。这就是读写锁

读写锁充许多个线程同时读,所以并 发性更好。读写锁具有以下特性:

  1. 多个读操作可以同时进行
  2. 写必须互斥,不充许两个写操作同时进行,也不能读、写操作同时进行。
  3. 写优先于读。在当前线程以读模式加锁后,其它线程进行读模式加锁,可以获得读的权限;在当前线程以读模式加锁后,其它线程加写锁时,将会堵塞,并且后继的读锁将会堵塞。这样可以避免读模式锁长期占用,导致写操作一直阻塞.
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • goroutine
  • 并发
  • channel
    • 单向channel
    • channel select
      • select 行为
      • channel 与 time 包结合使用
      • runtime 包在并发中的使用。
      • chan 使用 生产者/消费者
      • 进程同步
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档