goroutine并发扫描MySQL表_Golang学习笔记之并发.协程(Goroutine)、信道(Channel)

Go是并发语言,而不是并行语言。

一、并发和并行的区别•并发(concurrency)是指一次处理大量事情的能力。并发的关键是你有处理多个任务的能力,不一定要同时。

•并行(parallelism)指的是同时处理多个事情。并行的关键是你有同时处理多个任务的能力。

简单的理解一下,并发就是你在跑步的时候鞋带开了,你停下来系鞋带。而并行则是,你一边听歌一边跑步。

并行并不代表比并发快,举一个例子,当文件下载完成时,应该使用弹出窗口来通知用户。而这种通信发生在负责下载的组件和负责渲染用户界面的组件之间。在并发系统中,这种通信的开销很低。而如果这两个组件并行地运行在 CPU 的不同核上,这种通信的开销却很大。因此并行程序并不一定会执行得更快。

Go 原生支持并发。在Go中,使用 Go 协程(Goroutine)和信道(channel)来处理并发。

二、Go协程(Goroutine)

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员⽆需了解任何执⾏细节,调度器会⾃动将其安排到合适的系统线程上执⾏。协程是⼀种⾮常轻量级的实现,可在单个进程⾥执⾏成千上万的并发任务。•调度器不能保证多个 goroutine 执⾏次序,且进程退出时不会等待它们结束。

•Go 协程之间通过信道(channel)进行通信。

•协程里可以创建协程

(1)协程的创建package mainimport (    "fmt"

"time")func hello() {

fmt.Println("Hello world goroutine")

}func main() {    //开启了一个新的协程。hello() 函数将和 main() 函数一起运行。

go hello()

time.Sleep(1 * time.Second) //延时结束主程序,不然不能保证主程序会等协程程序

fmt.Println("main function")}(2)创建多个协程package mainimport (    "fmt"

"time")func numbers() {    for i := 1; i <= 5; i++ {

time.Sleep(250 * time.Millisecond)

fmt.Printf("%d ", i)

}

}func alphabets() {    for i := 'a'; i <= 'e'; i++ {

time.Sleep(400 * time.Millisecond)

fmt.Printf("%c ", i)

}

}func main() {    //numbers()和alphabets()并发执行

go numbers()

go alphabets()

time.Sleep(3000 * time.Millisecond)

fmt.Println("Main Over")}

输出:

1 a 2 3 b 4 c 5 d e Main Over(3)调⽤ runtime.Goexit()将⽴即终⽌当前 goroutine 执⾏。但所有已注册 defer延迟调⽤会被被执⾏。

修改一下上面的代码func alphabets() {

defer fmt.Println("结束") //defer会被调用

for i := 'a'; i <= 'e'; i++ {

time.Sleep(400 * time.Millisecond)

fmt.Printf("%c ", i)

runtime.Goexit() //立即结束该协程

}

}

程序输出则会变为

1 a 结束

2 3 4 5 Main Over

(4)调⽤ runtime.Gosched()将当前 goroutine 暂停,放回队列等待下次被调度执⾏。package mainimport (    "fmt"

"runtime")func main() {    go func() { //子协程   //没来的及执行主进程结束

for i := 0; i 

fmt.Println(i)

}

}()    for i := 0; i 

//让出时间片,先让别的协议执行,它执行完,再回来执行此协程

runtime.Gosched()

fmt.Println("执行")

}

}

三、信道(Channel)

信道(Channel)可以被认为是协程之间通信的管道。数据可以从信道的一端发送并在另一端接收。•默认为同步模式,需要发送和接收配对。否则会被阻塞,直到另⼀⽅准备好后被唤醒。

•信道支持单向信道

信道分为无缓冲信道和有缓冲信道

无缓冲信道:信道是同步的,接收前没有能力保存任何值。这种类型的信道只有发送和接收同时准备好,才能进行下次信道的操作,否则会导致阻塞。

有缓冲信道:信道是异步的,是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

信道声明

var ch chan T

我们声明了一个T类型的名称叫做ch的信道

信道的 0 值为 nil。我们需要通过内置函数 make 来创建一个信道,就像创建 map 和 slice 一样。

无缓冲信道

ch := make(chan T)

有缓冲信道

ch := make(chan T,cap)(1)信道的创建//内置类型channel

var a chan int

if a == nil {

a = make(chan int)

fmt.Printf("%T\n", a) //chan int

}    //自定义类型channel

var p chan person    if p == nil {

p = make(chan person) //chan main.person

fmt.Printf("%T\n", p)

}(2)通过信道发送和接收数据data := 

import (    "fmt"

"time")

func hello(done chan bool) {

fmt.Println("hello go routine is going to sleep")

time.Sleep(4 * time.Second)

//只有写数据后才能继续执行    done 

fmt.Println("hello go routine awake and going to write to done")

}

func main() {    done := make(chan bool)

go hello(done)

fmt.Println("Main received data")

}(4)死锁

使用信道是要考虑的一个重要因素是死锁(Deadlock)只读未写与只写未读都会触发死锁,并触发 panic 。channel 上如果发生了流入和流出不配对,就可能会发生死锁。package mainfunc main() {    ch := make(chan int)

ch 

}(6)单向信道与关闭信道close()

发送者可以关闭信道以通知接收者将不会再发送数据给信道。

v, ok :=

sendch 

//不能读

//

close(sendch) //显式关闭信道}//只读操作func readData(sendch 

}func main() {

sendch := make(chan int)    go sendData(sendch)

v, ok := 

v1, ok1 := 

fmt.Println(v, ok)   //10 true

fmt.Println(v1, ok1) //0 false}

(7)遍历信道

信道支持range for遍历package mainimport (    "fmt"

"time")func producer(chnl chan int) {    defer close(chnl) //程序执行结束关闭信道

for i := 0; i 

time.Sleep(300 * time.Millisecond) //一秒写一次

chnl 

}

}func main() {

ch := make(chan int)    go producer(ch)

//接收ch信道中的数据,直到该信道关闭。

for v := range ch {

fmt.Println(v)

}

}

也可以自定for循环遍历信道for {

v, ok := 

fmt.Println(v, ok)        if ok == false { //当读取不到数据跳出循环

break

}

}(8)缓冲信道

语法结构

ch := make(chan type, cap)

cap为容量。•缓冲信道支持len()和cap()

•只能向缓冲信道发送容量以内的数据

•只能接收缓冲信道长度以内的数据

信道是异步的,是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。func main() {    //创建一个容量为3的缓冲信道

ch := make(chan string, 3)

ch 

ch 

fmt.Println("capacity is", cap(ch))   //capacity is 3

fmt.Println("length is", len(ch))     //length is 2

fmt.Println("read value", 

fmt.Println("new length is", len(ch)) //new length is 1}(9)WaitGroup

假设我们有 3 个并发执行的 Go 协程(由Go 主协程生成)。Go 主协程需要等待这 3 个协程执行结束后,才会终止。这就可以用 WaitGroup 来实现。package mainimport (    "fmt"

"sync"

"time")func process(i int, wg *sync.WaitGroup) {

fmt.Println("started Goroutine ", i)

time.Sleep(2 * time.Second)

fmt.Printf("Goroutine %d ended\n", i)    //Done方法减少WaitGroup计数器的值,应在线程的最后执行。

wg.Done()

}/*

WaitGroup用于等待一组线程的结束。

父线程调用Add方法来设定应等待的线程的数量。

每个被等待的线程在结束时应调用Done方法。

同时,主线程里可以调用Wait方法阻塞至所有线程结束。

*/func main() {

no := 3

var wg sync.WaitGroup    //并发协程

for i := 0; i 

Add方法向内部计数加上delta,delta可以是负数;

如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,

如果计数器小于0,方法panic。

*/

wg.Add(1)        go process(i, &wg)

}    //Wait方法阻塞直到WaitGroup计数器减为0。

wg.Wait()

fmt.Println("over")}(9)select

select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞,直到发送/接收操作准备就绪。•如果有多个信道操作准备完毕,select 会随机地选取其中之一执行。

•空的select会触发死锁因此它会一直阻塞,导致死锁。package mainimport (    "fmt"

"time")func server1(ch chan string) {

time.Sleep(1 * time.Second)

ch 

time.Sleep(1 * time.Second)

ch 

output1 := make(chan string)

output2 := make(chan string)

go server1(output1)

go server2(output2)    //随机选择

select {    case s1 := 

fmt.Println(s1)    case s2 := 

fmt.Println(s2)

}

}

作者:学生黄哲

链接:https://www.jianshu.com/p/3c5f155818ec

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值