Golang并发的那些事(二)

目录

生产者和消费者模型

        使用通道只输出一万个值

                通道和goroutine配合处理指定数量数据

select多路复用

        select可以解决死锁问题

使用select完善生产者和消费者模型,键盘输入回车终止数据。

单向通道

                只写通道

                只读通道


生产者和消费者模型

  • 需求:
    
    计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6
    
    随机生成数字进行计算

第一种方式 

package main
​
import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
​
type Obj struct {
    id  int64
    num int64
}
​
type result struct {
    Obj *Obj
    sum int64
}
​
var sw sync.WaitGroup
var Objnum = make(chan *Obj, 100)
var SumNum = make(chan *result, 100)
​
//生产者函数,随机数
func PD(a chan *Obj) {
    var id int
    defer sw.Done()
​
    for {
        id++
        rand.Seed(time.Now().Unix())
        x := rand.Int63()
        new1 := &Obj{
            id:  int64(id),
            num: x,
        }
        a <- new1
        time.Sleep(time.Second)
    }
}
​
//消费者函数
func consume(ch1 chan *Obj, SumNum chan *result) {
​
    defer sw.Done()
    for {
        Obj := <-ch1
        Sum := int64(0)
        n := Obj.num
        for n > 0 {
            Sum += n % 10
            n = n / 10
        }
        newResult := &result{
            Obj: Obj,
            sum: Sum,
        }
        SumNum <- newResult
    }
}
func main() {
​
    sw.Add(1)
    go PD(Objnum)
​
    sw.Add(24)
    for i := 0; i < 24; i++ {
        go consume(Objnum, SumNum)
    }
    for sum := range SumNum {
        fmt.Printf("id号%d:,随机数:%d,和%d\n", sum.Obj.id,sum.Obj.num ,sum.sum)
    }
    sw.Wait()
}
​

第二种方式

package main
​
import (
    "fmt"
    "math/rand"
    //"os"
    "time"
)
​
//随机数通道
var itemChan chan *item
​
//求和管道
var resultChan chan *result
​
//随机数结构体
type item struct {
    id  int64
    num int64
}
​
//求和结构体
type result struct {
    item *item
    sum  int64
}
​
//生产者
func producer(ch chan *item) {
    var id int64
    for {
        id++
        //序列号自增
        number := rand.Int63() //随机正整数
        //随机数结构体封装
        tmp := &item{
            id:  id,
            num: number,
        }
        //随机数放入通道
        ch <- tmp
        // time.Sleep(time.Millisecond*100)
​
    }
}
​
//求和运算
func calc(i int64) int64 {
    //求和变量
    var sum int64
    for i > 0 {
        //每位数字求和
        sum = sum + i%10
        i = i / 10
    }
    return sum
}
​
//消费者
func consumer(ch chan *item, retChan chan *result) {
    for {
        //从itemChan通道中取随机数结构体指针
        tmp := <-ch
        //数据运算
        sum := calc(tmp.num)
        //求和结构体封装
        result := &result{
            item: tmp,
            sum:  sum,
        }
​
        //求和数放入resultChan通道
​
        retChan <- result
​
    }
}
​
//输出遍历
func printResult(resultChan chan *result) {
    for ret := range resultChan {
        fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
        //节奏输出控制
        time.Sleep(time.Second)
    }
​
}
​
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
    //开启n数量的goroutine
    for i := 0; i < n; i++ {
        //goroutine调用函数
        go consumer(ch, resultCh)
    }
}
​
func main() {
    //通道初始化,结构体指针类型
    itemChan = make(chan *item, 100)
    resultChan = make(chan *result, 100)
    //启用生产者goroutine
    go producer(itemChan)
    //消费者goroutine,高并发处理
    startWorker(100, itemChan, resultChan)
    //调用输出函数
    printResult(resultChan)
}
​

运行结果

他会一直执行下去

id:1;num:5577006791947779410;sum:95
id:2;num:8674665223082153551;sum:79
id:3;num:6129484611666145821;sum:81
id:4;num:4037200794235010051;sum:53
id:5;num:3916589616287113937;sum:95
id:6;num:6334824724549167320;sum:80
id:7;num:605394647632969758;sum:99
id:8;num:1443635317331776148;sum:77
id:9;num:894385949183117216;sum:89
id:10;num:2775422040480279449;sum:80
id:11;num:4751997750760398084;sum:99
id:12;num:7504504064263669287;sum:84
id:13;num:1976235410884491574;sum:88
id:14;num:3510942875414458836;sum:87
id:15;num:2933568871211445515;sum:80
id:16;num:4324745483838182873;sum:92
id:17;num:2610529275472644968;sum:89
....

        使用通道只输出一万个值

package main
​
import (
    "time"
    "fmt"
    "os"
    "os/signal"
    "sync"
)
​
var c chan os.Signal
var msgQueue chan *string
var wg sync.WaitGroup
​
func Producer(){
    i := 0
    LOOP:
    for{
        select {
        case s := <-c:
            fmt.Println()
            fmt.Println("Producer | get", s)
            break LOOP
        default:
        }
​
        i ++
        s := fmt.Sprintf("work-%d", i)
        fmt.Println("Producer | produce", s)
        msgQueue <- &s
        time.Sleep(500 * time.Millisecond)
    }
​
    close(msgQueue)
    fmt.Println("Producer | close channel, exit")
    wg.Done()
}
​
func Consumer(){
    for m := range msgQueue{
        if m != nil{
            fmt.Println("Consumer | consume", *m)
        }else{
            fmt.Println("Consumer | channel closed")
            break
        }
    }
    fmt.Println("Consumer | exit")
    wg.Done()
}
​
func main(){
    c = make(chan os.Signal, 1)
    msgQueue = make(chan *string, 1000)
    signal.Notify(c, os.Interrupt, os.Kill)
​
    //pruducer
    wg.Add(1)
    go Producer()
​
    //consumer
    wg.Add(1)
    go Consumer()
​
    wg.Wait()
}

                通道和goroutine配合处理指定数量数据

package main
​
import (
    "fmt"
    "math/rand"
)
​
//随机数通道
var itemChan chan *item
​
//求和管道
var resultChan chan *result
​
//随机数结构体
type item struct {
    id  int64
    num int64
}
​
//空结构体通道
var doneChan chan struct{}
​
//求和结构体
type result struct {
    item *item
    sum  int64
}
​
//生产者
func producer(itemCh chan *item) {
    var id int64
    //指定数量数据
    for i := 0; i < 10000; i++ {
​
        id++
        //序列号自增
        number := rand.Int63() //随机正整数
        //随机数结构体封装
        tmp := &item{
            id:  id,
            num: number,
        }
        //随机数放入通道
        itemCh <- tmp
        // time.Sleep(time.Millisecond*100)
    }
    //itemChan通道关闭
    close(itemCh)
}
​
//求和运算
func calc(i int64) int64 {
    //求和变量
    var sum int64
    for i > 0 {
        //每位数字求和
        sum = sum + i%10
        i = i / 10
    }
    return sum
}
​
//消费者
func consumer(ch chan *item, retChan chan *result) {
    for tmp := range ch {
        sum := calc(tmp.num)
        //求和结构体封装
        result := &result{
            item: tmp,
            sum:  sum,
        }
        //求和数放入resultChan通道
        retChan <- result
    }
    //传递空结构体进通道
    doneChan <- struct{}{}
}
​
//输出遍历
func printResult(resultChan chan *result) {
    for ret := range resultChan {
​
        fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
        //节奏输出控制
        //time.Sleep(time.Second)
    }
​
}
​
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
    //开启n数量的goroutine
    for i := 0; i < n; i++ {
        //goroutine调用函数
        go consumer(ch, resultCh)
    }
}
​
//监控goroutine及时关闭通道
func closeChan(n int, doneChan chan struct{}, resultChan chan *result) {
    //监控goroutine次数
    for i := 0; i < n; i++ {
        //取空结构体
        <-doneChan
    }
    //计数通道关闭
    close(doneChan)
    //求和通道关闭
    close(resultChan)
}
​
func main() {
    //通道初始化,结构体指针类型
    itemChan = make(chan *item, 10000)
    resultChan = make(chan *result, 10000)
    doneChan = make(chan struct{}, 30)
    //启用生产者goroutine
    go producer(itemChan)
    //消费者goroutine,高并发处理
    startWorker(30, itemChan, resultChan)
    //启动gorountine监控流程是否结束
    go closeChan(30, doneChan, resultChan)
    //调用输出函数
    printResult(resultChan)
}
​

只输出一万个,因为是并发,不按照顺序,其实到一万个数自动停止

...
id:7192;num:6762665259437917828;sum:103
id:7187;num:5802099863961749885;sum:107
id:7193;num:3024088589606596663;sum:94
id:7184;num:9012798112107155725;sum:73
id:7185;num:7445733868822300479;sum:90
id:7173;num:7990700824623006162;sum:72
id:9982;num:6793585068966742459;sum:109
id:9962;num:1266029665194304063;sum:73
id:7189;num:5551919529326822013;sum:78
id:7178;num:3895404382820407125;sum:75
id:9833;num:4616123227651317754;sum:73
id:7174;num:4240505806083476750;sum:74
id:7182;num:7571595319875649637;sum:107
id:7172;num:837347738337784340;sum:89
id:9953;num:5568458834365856656;sum:106
id:9844;num:3606295569004564335;sum:81

select多路复用

类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。

select 随机执行一个可运行的case。如果没有case 可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

某些场景下,我们需要同事从多个通道接受数据,没有数据发生接收就会阻塞,我们往往可以使用一下方式解决。

语法示例

select {
​
    case communication clause:
​
    statement(s);
​
    case communication clause :
​
    statement(s);
​
    /*你可以定义任意数量的case*/
    default :/*可选*/
​
    statement(s);
}

示例

·每个case都必须是一个通信·所有channel表达式都会被求值·所有被发送的表达式都会被求值
·如果任意某个通信可以进行,它就执行,其他被忽略。
·如果有多个case都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
​
    1.如果有default子句,则执行该语句。
    2如果没有default子句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
​
package main
​
import (
    "fmt"
    "math"
    "time"
)
​

//定义两个通道
var ch1 = make(chan string, 100)
​
var ch2 = make(chan string, 100)
​
func sendK1(ch chan string) {
    //产生数据
    for i := 0; i < math.MaxInt64; i++ {
        //向ch中发送数据
        ch <- fmt.Sprintf("k1:%d", i)
        //50毫秒放一个数据
        time.Sleep(time.Millisecond * 50)
    }
}
​
func sendK2(ch chan string) {
    //产生数据
    for i := 0; i < math.MaxInt64; i++ {
        //向ch中发送数据
        ch <- fmt.Sprintf("k2:%d", i)
        //100毫秒放一个数据
        time.Sleep(time.Millisecond * 100)
    }
}
​
func main() {
​
    go sendK1(ch1)
​
    time.Sleep(time.Millisecond * 100)
    go sendK2(ch2)
​
    for {
        /*如果通道都可以通信,随机公平执行其中一条,忽略其他,如果通道都不能通信,
        且没有default语句,处于阻塞状态,直到可以通信为止*/
        select {
        case ret := <-ch1:
            fmt.Println(ret)
        case ret := <-ch2:
            fmt.Println(ret)
        default:
            fmt.Println("没有其他数据可取")
​
            time.Sleep(time.Millisecond * 500)
        }
​
    }
​
}
​
运行结果

PS D:\golang\src\day27\select\demo1> go run .\select.go
k1:0
k1:1
k2:0
没有其他数据可取
k2:1
k1:2
k1:3
k2:2
k2:3
k1:4
k1:5
k2:4
k1:6
k1:7
k1:8
k1:9
k1:10
没有其他数据可取
k1:11
k2:5
k1:12
k2:6
k2:7
k1:13
k1:14
k1:15
k1:16
k1:17
k1:18
k2:8
k2:9
没有其他数据可取
k1:19
k1:20
k1:21
k1:22
k1:23
k1:24
k2:10
k1:25
k2:11
k1:26
k2:12
k2:13
没有其他数据可取
k1:27
k2:14
k2:15
k1:28
k2:16
k1:29
k1:30
k1:31
k1:32
k1:33
k1:34
k2:17
k2:18
没有其他数据可取
k2:19
k1:35
k1:36
k2:20
k2:21
k1:37
k1:38
k2:22
k2:23
k1:39
k1:40
k1:41
k1:42
没有其他数据可取
k2:24
k1:43
k1:44
k2:25
k2:26
k2:27
k1:45
k1:46
k1:47
k1:48
k1:49
k1:50
没有其他数据可取
k1:51
k1:52
k1:53
k2:28
k2:29
k1:54
k1:55
k1:56
k2:30
k2:31
k1:57
k2:32
k1:58
没有其他数据可取
exit status 0xc000013a 
ctrl+c停止

        select可以解决死锁问题

package main
​
import (
    "fmt"
)
​
func main() {
​
    var ch = make(chan int, 1)
    for i := 0; i < 10; i++ {
​
        //可以解决死锁
        select {
​
        case ch <- i:
​
            //fmt.Println("处理不了")
        case ret := <-ch:
            fmt.Println(ret)
        }
    }
}
​
运行结果

0
2
4
6
8

使用select完善生产者和消费者模型,键盘输入回车终止数据。

package main
​
import (
    "fmt"
    "math"
    "os"
    "time"
)
​
var b = make([]byte, 100)
​
//定义两个通道
var quit = make(chan string, 100)
​
var ch2 = make(chan string, 100)
​
var ch3 = make(chan string, 100)
​
func sendK1(ch chan string) {
    //产生数据
    for i := 0; i < math.MaxInt64; i++ {
        //向ch中发送数据
        ch <- fmt.Sprintf("k1:%d", i)
        //50毫秒放一个数据
        //time.Sleep(time.Millisecond * 50)
    }
}
​
func sendK2(ch chan string) {
    //产生数据
    for i := 0; i < math.MaxInt64; i++ {
        //向ch中发送数据
        ch <- fmt.Sprintf("k2:%d", i)
        //100毫秒放一个数据
        //time.Sleep(time.Millisecond * 1000)
    }
}
//回车退出
func sendK3(ch chan string) {
    for i := 0; ; i++ {
        fmt.Scanf("%c", &b[i])
        fmt.Print(string(b[i]))
        if b[i] == '\n' {
            os.Exit(0)
​
            ch <- string(b)
        }
    }
}
​
func main() {
​
    go sendK1(quit)
​
    time.Sleep(time.Millisecond * 100)
    go sendK2(ch2)
​
    go sendK3(ch3)
​
    for {
        /*如果通道都可以通信,随机公平执行其中一条,忽略其他,如果通道都不能通信,
        且没有default语句,处于阻塞状态,直到可以通信为止*/
        select {
        case ret := <-quit:
            fmt.Println(ret)
            //return
        case ret := <-ch2:
            fmt.Println(ret)
​
        case <-ch3:
​
        default:
            fmt.Println("没有其他数据可取")
            //time.Sleep(time.Millisecond * 500)
            //遇见回车退出
​
        }
​
    }
}
​
运行结果

回车可停

第二种方式

package main
​
import (
    "fmt"
    "math/rand"
    "os"
)
​
//空格退出
//随机数通道
var itemChan chan *item
​
//求和管道
var resultChan chan *result
​
var exitChan chan struct{}
​
//随机数结构体
type item struct {
    id  int64
    num int64
}
​
//求和结构体
type result struct {
    item *item
    sum  int64
}
​
//生产者
func producer(itemCh chan *item) {
    var id int64
    //指定数量数据
    for {
​
        id++
        //序列号自增
        number := rand.Int63() //随机正整数
        //随机数结构体封装
        tmp := &item{
            id:  id,
            num: number,
        }
        //随机数放入通道
        itemCh <- tmp
        // time.Sleep(time.Millisecond*100)
    }
}
​
//求和运算
func calc(i int64) int64 {
    //求和变量
    var sum int64
    for i > 0 {
        //每位数字求和
        sum = sum + i%10
        i = i / 10
    }
    return sum
}
​
//消费者
func consumer(ch chan *item, retChan chan *result) {
    //通知main计数-1
    for tmp := range ch {
        sum := calc(tmp.num)
        //求和结构体封装
        result := &result{
            item: tmp,
            sum:  sum,
        }
        //求和数放入resultChan通道
        retChan <- result
    }
}
​
//输出遍历
func printResult(exitChan chan struct{}, resultChan chan *result) {
    for {
        //控制台控制数据开关
        select {
        //消费数据输出通道
        case ret := <-resultChan:
            fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
            //time.Sleep(time.Second)
        //控制台数据通道
        case <-exitChan:
            return
        }
    }
​
}
​
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
    //开启n数量的goroutine
    for i := 0; i < n; i++ {
        //goroutine调用函数
        go consumer(ch, resultCh)
    }
}
​
//控制台输入
func inputer(exitChan chan struct{}) {
    //字节数组
    tmp := [1]byte{}
    //从控制台输入
    os.Stdin.Read(tmp[:])
    //向exitChan中传入数据
    exitChan <- struct{}{}
​
}
​
func main() {
    //通道初始化,结构体指针类型
    itemChan = make(chan *item, 10000)
    resultChan = make(chan *result, 10000)
    exitChan = make(chan struct{}, 1)
    //启用生产者goroutine
    go producer(itemChan)
    //消费者goroutine,高并发处理
    startWorker(30, itemChan, resultChan)
​
    go inputer(exitChan)
    //调用输出函数
    printResult(exitChan, resultChan)
}
​

运行结果

这种方式回车不会立刻停止,因为信道里面还有数据没有全部取出来,他会在你回车的时候将剩下的数据释放出来,意义上实现了回车停止

id:5353,num:982834177255573862,sum:92
id:5354,num:7291943030372177817,sum:81
id:5356,num:3500252379460637325,sum:72
id:5345,num:7035598168391094882,sum:96
id:5357,num:3456275693262101224,sum:70
​
id:5358,num:5845323045055441055,sum:68
id:5359,num:6781058790380715290,sum:86
id:5360,num:774311384505828488,sum:86
id:5361,num:7973701242218090496,sum:81

单向通道

在函数中只能发送值不能接收值称之为只写通道,只能接收不能发送值称之为只读通道,让代码意向更明确,更清晰,

                只写通道

package main
​
import "fmt"
​
//只写通道
func main() {
    var ch chan<- int
​
    for  i := 0;  i < 10;  i++ {
        ch <-i
    }
​
    ret := <-ch //会报错,语法错误
​
    fmt.Println(ret)
}

                只读通道

package main
​
import "fmt"
​
//只写通道
func main() {
    var ch <-chan int
​
    for  i := 0;  i < 10;  i++ {
        ch <-i //invalid operation: cannot send to receive-only type <-chan int
    }
}

单向通道只在自己的函数生效

示例

package main
​
import "fmt"
​
var ch chan int
​
//只写通道
func warit(ch chan<- int) {
​
    ch <- 10
}
​
//只读通道
func readch(ch <-chan int) int {
    ret := <-ch
    return ret
}
func main() {
​
    ch = make(chan int, 1)
​
    warit(ch)
    //ret := <-ch
    //fmt.Println(ret)
    fmt.Println(readch(ch))
}
​
运行结果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小柏ぁ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值