Golang并发的一些事(一)

目录

基础概念

        sync.WaitGroup

                多goroutine执行

                panic测试

Goroutine和线程

                可增长的栈

                goroutine调度

                GOMAXPROCS

        channel

无缓存通道和缓存通道

        示例,无缓存通道

        缓存区通道

                判断是否取完值


基础概念

并发:是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。同一时间段同时在做多个事情。

并行:在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。同一时刻同时在做多个事情。

进程:一个程序启动之后就创建了—个进程。

线程:操作系统调度的最小单元。

协程:用户态的线程。

GoRoutine: go关键字为一个函数创建一个goroutine。一个函数可以被创建多个goroutine,一个goroutine必定对应一个函数。 

示例:启动单个goroutine,只需在调用的函数(普通函数和匿名函数)前面加上一个go关键字。

 

package main
​
import "fmt"
​
func hello(){
​
    fmt.Println("hello函数")
func main() {
​
    //并发执行
    go hello()
​
    fmt. Printin("main函数")
}
​
运行结果

main函数
等待一秒钟

package main
​
import (
    "fmt"
    //"time"
)
​
func hello() {
    fmt.Println("hello函数")
}
func main() {
​
    go hello() //启用了一个goroutine
    //等待一秒
    //time.Sleep(time.Second)
    fmt.Println("main函数")
}
​
运行结果

hello函数
main函数

示例:加入defer语句。

package main
​
import (
    "fmt"
    "time"
)
​
func hello() {
​
    fmt.Println("hello函数")
}
​
func main() {
​
    defer fmt.Println("defer语句") //main函数结束前执行
​
    go hello() //:先创建一个goroutinej 2:再goroutine中执行hello函数。
    fmt.Println("main函数")
​
    time.Sleep(time.Second) //等待一秒主程序再结束
}
​
延迟执行defer语句

main函数
hello函数
defer语句

        sync.WaitGroup

使用sync WaitGroup无需设置等待时间,它会自动等待所有goroutine执行完成后再结束main,效率提升。

package main
​
import (
    "fmt"
    "sync"
)
//定义sync.waitGroup函数,内置计数器
var sw sync.WaitGroup
​
func hello() {
    fmt.Println("hello函数")
​
    //告知计数器-1,执行完通知
    sw.Done()
}
func test() {
    fmt.Println("test函数")
​
    sw.Done()
}
func main() {
​
    defer fmt.Println("defer语句")
    //设置计数器次数
    sw.Add(2)
    go hello()
    go test()
    fmt.Println("main函数")
    //等待计数器归零,结束main函数,否则一直处于等待状态
    sw.Wait()
}
​
运行结果

hello函数
main函数
test函数
defer语句

                多goroutine执行

package main
​
import (
    "fmt"
    "sync"
)
​
var sw sync.WaitGroup
​
func hello(i int) {
    fmt.Println("抢购商品", i)
    sw.Done()
}
​
func main() {
​
    sw.Add(10)
    for i := 0; i < 10; i++ {
        go hello(i)
    }
​
    //等待机制
    sw.Wait()
}
​
运行结果

抢购商品 0
抢购商品 6
抢购商品 5
抢购商品 7
抢购商品 9
抢购商品 8
抢购商品 1
抢购商品 2
抢购商品 4
抢购商品 3

                panic测试

示例:panic宕机前把错误信息发送到控制台上,程序结束,资源全部释放。

package main
​
import (
    "fmt"
    "sync"
)
​
//定义sync. waitGroup结构体,内置计数器
var sw sync.WaitGroup
​
func hello(i int) {
​
    //defer sw. Done()消亡前计数-1 goroutine会全部执行完
    fmt.Println("hello函数", i)
​
    if i == 8 {
        panic("宕机报警")
    }
​
    sw.Done() //遇到panic不执行
}
func main() {
​
    defer fmt.Println("defer语句") //启用10个goroutine
​
    sw.Add(10)
​
    for i := 0; i < 10; i++ {
​
        go hello(i)
    }
    sw.Wait()
}
​
运行结果

hello函数 0
hello函数 9
hello函数 5
hello函数 6
hello函数 7
hello函数 8
hello函数 2
hello函数 1
hello函数 3
panic: 宕机报警
​
goroutine 14 [running]:
main.hello(0x8)
    d:/golang/src/day25/doue goroutine/demo1/main.go:17 +0xb7
created by main.main
    d:/golang/src/day25/doue goroutine/demo1/main.go:30 +0xb0
exit status 2

Goroutine和线程

                可增长的栈

OS线程(操作系统线程)一般都有固定的栈内存(2MB),一个goroutine的栈在生命周期开始时只有很小的栈 (2KB),goroutine的栈是不固定的,可以按需增加或者缩小,goroutine的栈大小限制可以达到1GB,虽然这种情况不多见,所以一次可以创建十万左右的goroutine是没问题的。

                goroutine调度

OS线程由OS内核来调度,goroutine则是由Go运行时(runtime)自己的调度器来调度,这个调度器使用一个m:n调度的技术(复用/调度m个goroutine到n个OS线程),goroutine的调度不需要切换内核语境,所以调用一个goroutine比调用一个线程的成本要低很多。

                GOMAXPROCS

Go运行时的调度使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码,默认值是机器上的CPU核心数。例如:在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)

Go可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。(Go1.5版本前默认是单核心执行,Go1.5版本后默认使用全部逻辑核心数)

示例:通过将任务分配到不同的CPU逻辑核心上实现并行的效果。

package main
​
import (
    "fmt"
    "runtime"
    "sync"
)
​
var sw sync.WaitGroup
​
func a() {
​
    defer sw.Done()
    for i := 0; i < 10; i++ {
        fmt.Println("A", i)
    }
​
}
​
func b() {
​
    defer sw.Done()
    for i := 0; i < 20; i++ {
        fmt.Println("B", i)
    }
​
}
func main() {
    //调度
    runtime.GOMAXPROCS(2)
    sw.Add(2)
    
    go b()
    go a()
    //time.Sleep(time.Second)
    sw.Wait()
}
​
运行结果

PS D:\golang\src\day25\Even> go run .\main.go
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
B 12
B 13
B 14
B 15
B 16
B 18
B 19
PS D:\golang\src\day25\Even> go run .\main.go
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
B 12
B 13
B 14
B 15
B 16
B 18
B 19
PS D:\golang\src\day25\Even> go run .\main.go
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
B 12
B 13
B 14
B 15
B 16
B 17
B 18
B 19
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 8
A 9

        channel

        单纯的将函数并发执行没有意义。函数与函数间需要交换数据才能体现并发执行函数的意义。

        虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

go语言的并发模型是SCP,提倡通过通信共享内存而不是通过共享内存而实现通信。

如果说goroutine是Go程序并发的执行体,channel就是它们之间连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

go语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,在声明channel的时候需要为其指定元素类型。

声明语法:

var变量chan 元素类型

示例

var ch1 chan int//传递整型的通道
var ch2 chan bool //传递布尔型的通道
var ch3 chan []int//传递整型切片的通道

初始值类型

package main
​
import "fmt"
​
//channel定义
​
func main() {
    var ch1 chan int    //通道ch1传输int元素数据
    var ch2 chan string //通道ch2传输string元素数据
​
    fmt.Println("ch1:", ch1)
    fmt.Println("ch2:", ch2)
}
​
运行结果

ch1: <nil>
ch2: <nil>

示例添加值

package main
​
import "fmt"
​
//channel定义
​
func main() {
    var ch1 chan int    //通道ch1传输int元素数据
    var ch2 chan string //通道ch2传输string元素数据
​
    fmt.Println("ch1:", ch1)
    fmt.Println("ch2:", ch2)
​
    //make初始化channel
    ch3 := make(chan int, 1)
    //发送
​
    ch3 <- 10
    //接收
    result := <-ch3
    fmt.Println("ch3:", result)
}
​
运行结果

ch1: <nil>
ch2: <nil>
ch3: 10
package main
​
import "fmt"
​
//channel定义
​
func main() {
    var ch1 chan int    //通道ch1传输int元素数据
    var ch2 chan string //通道ch2传输string元素数据
​
    fmt.Println("ch1:", ch1)
    fmt.Println("ch2:", ch2)
​
    //make初始化channel
    ch3 := make(chan int, 2)
    //发送
​
    ch3 <- 10
    ch3 <- 20
    //接收
    result := <-ch3
    result1 := <-ch3
    fmt.Println("ch3:", result, result1)
​
    //关闭ch3
    close(ch3)
    //关闭信道,是否能添加值,不被运行
    //ch3 <-30
    result = <-ch3
    fmt.Println("关闭之后result的值:",result)
​
}
​
运行结果

ch1: <nil>
ch2: <nil>
ch3: 10 20
关闭之后result的值: 0

关闭信道,再取值,可以正常取值,再做改动都会崩溃报panic(添加值、再次关闭信道),

死锁:①:make空间为1,但你传入信道的值大于1会出现死锁

//make初始化channel
    ch3 := make(chan int, 1)
    //发送
​
    ch3 <- 10
    ch3 <- 20
    result := <-ch3
fatal error : all goroutines are asleep - deadlock!
​

②:当channel没有值,取值的时候会出现死锁

示例

package main
​
import "fmt"
​
func main() {
    var ch = make(chan int, 10)
    for i := 0; i < 10; i++ {
        ch <- i
        if i == 6 {
            close(ch)
            break
        }
    }
​
    leng := len(ch)
    fmt.Printf("个数为%d,容量为:%d\n", leng, cap(ch))
​
    //数据量不确定的情况下取数据
    for result := range ch {
        fmt.Println(result)
    }
​
    // for i := 0; i < leng; i++ {
    //  result := <-ch
    //  fmt.Println(result)
    // }
​
}
​
运行结果

个数为7,容量为:10
0
1
2
3
4
5
6

无缓存通道和缓存通道

        示例,无缓存通道

package main
​
import (
    "fmt"
    "time"
)
​
//传递值
func Value(ch chan bool) {
    //取值,未取到值会阻塞
    rel := <-ch
    fmt.Println(rel)
}
func main() {
​
    //设置缓存区,异步执行
    ch := make(chan bool)
​
    //同步执行
    go Value(ch)
​
    ch <- true
    fmt.Println("main函数")
​
}
​
运行结果

true
mian函数

        缓存区通道

package main
​
import (
    "fmt"
    "time"
)
​
//传递值
func Value(ch chan bool) {
    //取值,未取到值会阻塞
    rel := <-ch
    fmt.Println(rel)
}
func main() {
​
    //设置缓存区,异步执行
    ch := make(chan bool, 10)
​
    ch <- false
    fmt.Printf("个数:%d,容量:%d\n", len(ch), cap(ch))
    //同步执行
    go Value(ch)
​
    ch <- true
    time.Sleep(time.Second)
    fmt.Println("main函数")
​
}
个数:1,容量:10
false
main函数

                判断是否取完值

package main
​
import "fmt"
​
var ch1 chan int
​
func send(ch chan int) {
​
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}
func main() {
    //初始化
    ch1 = make(chan int, 100)
​
    go send(ch1)
​
    for ret := range ch1 {
        fmt.Println(ret)
    }
    //将取到的值放入道chan当中
    // for {
    //  ret, ok := <-ch1
​
    //  //判断是否取完值
    //  if !ok {
    //      break
    //  }
    //  fmt.Println(ret)
    // }
}
​
运行结果

0
1
2
3
4
5
6
7
8
9

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小柏ぁ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值