热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

gochannel缓冲区最大限制_Golang学习笔记之并发.协程(Goroutine)、信道(Channel)

原文作者:学生黄哲来源:简书Go是并发语言,而不是并行语言。一、并发和并行的区别•并发(concurrency)是指一次处理大量事情的能力

37a66ee9de2f1ee45f1bae0f123f2bc1.png

原文作者:学生黄哲
来源:简书

Go是并发语言,而不是并行语言。
一、并发和并行的区别

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

简单的理解一下,并发就是你在跑步的时候鞋带开了,你停下来系鞋带。而并行则是,你一边听歌一边跑步。
并行并不代表比并发快,举一个例子,当文件下载完成时,应该使用弹出窗口来通知用户。而这种通信发生在负责下载的组件和负责渲染用户界面的组件之间。在并发系统中,这种通信的开销很低。而如果这两个组件并行地运行在 CPU 的不同核上,这种通信的开销却很大。因此并行程序并不一定会执行得更快。Go 原生支持并发。在Go中,使用 Go 协程(Goroutine)和信道(channel)来处理并发。

二、Go协程(Goroutine)

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员⽆需了解任何执⾏细节,调度器会⾃动将其安排到合适的系统线程上执⾏。协程是⼀种⾮常轻量级的实现,可在单个进程⾥执⾏成千上万的并发任务。

•调度器不能保证多个 goroutine 执⾏次序,且进程退出时不会等待它们结束。
•Go 协程之间通过信道(channel)进行通信。
•协程里可以创建协程

(1)协程的创建

1package main
2import (
3    "fmt"
4    "time"
5)
6func hello() {
7    fmt.Println("Hello world goroutine")
8}
9func main() {
10    //开启了一个新的协程。hello() 函数将和 main() 函数一起运行。
11    go hello()
12    time.Sleep(1 * time.Second) //延时结束主程序,不然不能保证主程序会等协程程序
13    fmt.Println("main function")
14}

(2)创建多个协程

1package main
2import (
3    "fmt"
4    "time"
5)
6func numbers() {
7    for i :&#61; 1; i <&#61; 5; i&#43;&#43; {
8        time.Sleep(250 * time.Millisecond)
9        fmt.Printf("%d ", i)
10    }
11}
12func alphabets() {
13    for i :&#61; &#39;a&#39;; i <&#61; &#39;e&#39;; i&#43;&#43; {
14        time.Sleep(400 * time.Millisecond)
15        fmt.Printf("%c ", i)
16    }
17}
18func main() {
19    //numbers()和alphabets()并发执行
20    go numbers()
21    go alphabets()
22    time.Sleep(3000 * time.Millisecond)
23    fmt.Println("Main Over")
24}

输出&#xff1a;
1 a 2 3 b 4 c 5 d e Main Over

(3)调⽤ runtime.Goexit()将⽴即终⽌当前 goroutine 执⾏。但所有已注册 defer延迟调⽤会被被执⾏。

修改一下上面的代码

1func alphabets() {
2    defer fmt.Println("结束") //defer会被调用
3    for i :&#61; &#39;a&#39;; i <&#61; &#39;e&#39;; i&#43;&#43; {
4        time.Sleep(400 * time.Millisecond)
5        fmt.Printf("%c ", i)
6        runtime.Goexit() //立即结束该协程
7    }
8}

程序输出则会变为
1 a 结束
2 3 4 5 Main Over

(4)调⽤ runtime.Gosched()将当前 goroutine 暂停&#xff0c;放回队列等待下次被调度执⾏。

1package main
2import (
3    "fmt"
4    "runtime"
5)
6func main() {
7    go func() { //子协程   //没来的及执行主进程结束
8        for i :&#61; 0; i 5; i&#43;&#43; {
9            fmt.Println(i)
10        }
11    }()
12
13    for i :&#61; 0; i 2; i&#43;&#43; { //默认先执行主进程主进程执行完毕
14        //让出时间片&#xff0c;先让别的协议执行&#xff0c;它执行完&#xff0c;再回来执行此协程
15        runtime.Gosched()
16        fmt.Println("执行")
17    }
18}

三、信道(Channel)

信道(Channel)可以被认为是协程之间通信的管道。数据可以从信道的一端发送并在另一端接收。

•默认为同步模式&#xff0c;需要发送和接收配对。否则会被阻塞&#xff0c;直到另⼀⽅准备好后被唤醒。
•信道支持单向信道

信道分为无缓冲信道和有缓冲信道无缓冲信道&#xff1a;信道是同步的&#xff0c;接收前没有能力保存任何值。这种类型的信道只有发送和接收同时准备好&#xff0c;才能进行下次信道的操作&#xff0c;否则会导致阻塞。有缓冲信道&#xff1a;信道是异步的&#xff0c;是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据&#xff0c;或还包含可以接收的数据&#xff0c;那么其通信就会无阻塞地进行。

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

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

无缓冲信道ch :&#61; make(chan T)有缓冲信道ch :&#61; make(chan T,cap)

(1)信道的创建

1//内置类型channel
2    var a chan int
3    if a &#61;&#61; nil {
4        a &#61; make(chan int)
5        fmt.Printf("%T\n", a) //chan int
6    }
7    //自定义类型channel
8    var p chan person
9    if p &#61;&#61; nil {
10        p &#61; make(chan person) //chan main.person
11        fmt.Printf("%T\n", p)
12    }

(2)通过信道发送和接收数据

1data :&#61; // 从信道 a 中读取数据并将读取的值赋值给变量 data 。
2a data // 向信道 a 中写入数据。

(3)发送和接收默认是阻塞的

1package main
2import (
3    "fmt"
4    "time"
5)
6func hello(done chan bool) {
7    fmt.Println("hello go routine is going to sleep")
8    time.Sleep(4 * time.Second)
9    //只有写数据后才能继续执行
10    done true
11    fmt.Println("hello go routine awake and going to write to done")
12}
13func main() {
14    done :&#61; make(chan bool)
15    go hello(done)
16    17    fmt.Println("Main received data")
18}

(4)死锁

使用信道是要考虑的一个重要因素是死锁(Deadlock)只读未写与只写未读都会触发死锁&#xff0c;并触发 panic 。

channel 上如果发生了流入和流出不配对&#xff0c;就可能会发生死锁。

1package main
2func main() {
3    ch :&#61; make(chan int)
4    ch 5    //只写未读触发死锁
5}

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

发送者可以关闭信道以通知接收者将不会再发送数据给信道。v, ok :&#61; 判断信道是否已关闭

1package main
2import (
3    "fmt"
4)
5//只写操作
6func sendData(sendch chanint) {
7    sendch 10
8
9    //不能读
10    //1112    close(sendch) //显式关闭信道13}14//只读操作15func readData(sendch chan int) {
16    17}
18func main() {
19    sendch :&#61; make(chan int)
20    go sendData(sendch)
21    v, ok :&#61; //ok 返回 true 表示成功的接收到了发送的数据&#xff0c;如果 ok 返回 false 则表示信道已经被关闭。
22    v1, ok1 :&#61; 23    fmt.Println(v, ok)   //10 true
24    fmt.Println(v1, ok1) //0 false
25}

(7)遍历信道

信道支持range for遍历

1package main
2
3import (
4    "fmt"
5    "time"
6)
7
8func producer(chnl chan int) {
9    defer close(chnl) //程序执行结束关闭信道
10    for i :&#61; 0; i 10; i&#43;&#43; {
11        time.Sleep(300 * time.Millisecond) //一秒写一次
12        chnl //写操作
13    }
14}
15func main() {
16    ch :&#61; make(chan int)
17    go producer(ch)
18    //接收ch信道中的数据&#xff0c;直到该信道关闭。
19    for v :&#61; range ch {
20        fmt.Println(v)
21    }
22}

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

1for {
2        v, ok :&#61; //读操作
3        fmt.Println(v, ok)
4        if ok &#61;&#61; false { //当读取不到数据跳出循环
5            break
6        }
7    }

(8)缓冲信道

语法结构ch :&#61; make(chan type, cap)
cap为容量。

•缓冲信道支持len()和cap()
•只能向缓冲信道发送容量以内的数据
•只能接收缓冲信道长度以内的数据

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

1func main() {
2    //创建一个容量为3的缓冲信道
3    ch :&#61; make(chan string, 3)
4    ch "naveen"
5    ch "paul"
6    fmt.Println("capacity is", cap(ch))   //capacity is 3
7    fmt.Println("length is", len(ch))     //length is 2
8    fmt.Println("read value", //read value naveen
9    fmt.Println("new length is", len(ch)) //new length is 1
10}

(9)WaitGroup

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

1package main
2
3import (
4    "fmt"
5    "sync"
6    "time"
7)
8
9func process(i int, wg *sync.WaitGroup) {
10    fmt.Println("started Goroutine ", i)
11    time.Sleep(2 * time.Second)
12    fmt.Printf("Goroutine %d ended\n", i)
13    //Done方法减少WaitGroup计数器的值&#xff0c;应在线程的最后执行。
14    wg.Done()
15}
16
17/*18    WaitGroup用于等待一组线程的结束。19    父线程调用Add方法来设定应等待的线程的数量。20    每个被等待的线程在结束时应调用Done方法。21    同时&#xff0c;主线程里可以调用Wait方法阻塞至所有线程结束。22*/
23func main() {
24    no :&#61; 3
25    var wg sync.WaitGroup
26    //并发协程
27    for i :&#61; 0; i 28        /*29            Add方法向内部计数加上delta&#xff0c;delta可以是负数&#xff1b;30            如果内部计数器变为0&#xff0c;Wait方法阻塞等待的所有线程都会释放&#xff0c;31            如果计数器小于0&#xff0c;方法panic。32        */
33        wg.Add(1)
34        go process(i, &wg)
35    }
36    //Wait方法阻塞直到WaitGroup计数器减为0。
37    wg.Wait()
38    fmt.Println("over")
39}

(10)select

select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞&#xff0c;直到发送/接收操作准备就绪。

•如果有多个信道操作准备完毕&#xff0c;select 会随机地选取其中之一执行。
•空的select会触发死锁因此它会一直阻塞&#xff0c;导致死锁。

1package main
2import (
3    "fmt"
4    "time"
5)
6func server1(ch chan string) {
7    time.Sleep(1 * time.Second)
8    ch "from server1"
9}
10func server2(ch chan string) {
11    time.Sleep(1 * time.Second)
12    ch "from server2"
13}
14func main() {
15    output1 :&#61; make(chan string)
16    output2 :&#61; make(chan string)
17    go server1(output1)
18    go server2(output2)
19    //随机选择
20    select {
21    case s1 :&#61; 22        fmt.Println(s1)
23    case s2 :&#61; 24        fmt.Println(s2)
25    }
26}

版权申明&#xff1a;内容来源网络&#xff0c;版权归原创者所有。除非无法确认&#xff0c;我们都会标明作者及出处&#xff0c;如有侵权烦请告知&#xff0c;我们会立即删除并表示歉意。谢谢。

7e7120e9d19eeebcd284c096e4c60bbd.png

Golang语言社区

ID&#xff1a;GolangWeb

www.ByteEdu.Com

游戏服务器架构丨分布式技术丨大数据丨游戏算法学习

80af0118f3cd13c51aef0d77e0fa83b9.png




推荐阅读
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 基于Node.js、Express、MongoDB和Socket.io的实时聊天应用开发
    本文详细介绍了使用Node.js、Express、MongoDB和Socket.io构建的实时聊天应用程序。涵盖项目结构、技术栈选择及关键依赖项的配置。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 全面解析运维监控:白盒与黑盒监控及四大黄金指标
    本文深入探讨了白盒和黑盒监控的概念,以及它们在系统监控中的应用。通过详细分析基础监控和业务监控的不同采集方法,结合四个黄金指标的解读,帮助读者更好地理解和实施有效的监控策略。 ... [详细]
  • 深入解析Serverless架构模式
    本文将详细介绍Serverless架构模式的核心概念、工作原理及其优势。通过对比传统架构,探讨Serverless如何简化应用开发与运维流程,并介绍当前主流的Serverless平台。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 本文详细介绍了一种通过MySQL弱口令漏洞在Windows操作系统上获取SYSTEM权限的方法。该方法涉及使用自定义UDF DLL文件来执行任意命令,从而实现对远程服务器的完全控制。 ... [详细]
  • 离线安装Grafana Cloudera Manager插件并监控CDH集群
    本文详细介绍如何离线安装Cloudera Manager (CM) 插件,并通过Grafana监控CDH集群的健康状况和资源使用情况。该插件利用CM提供的API接口进行数据获取和展示。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • 在编译BSP包过程中,遇到了一个与 'gets' 函数相关的编译错误。该问题通常发生在较新的编译环境中,由于 'gets' 函数已被弃用并视为安全漏洞。本文将详细介绍如何通过修改源代码和配置文件来解决这一问题。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 探讨了一个关于使用多线程实现从0累加至1000的面试题,分析了在不同线程数量下结果出现偏差的原因,并提供了修正方案。 ... [详细]
author-avatar
我等到你不再等我_129
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有