广告
返回顶部
首页 > 资讯 > 数据库 >Go语言中的并发是什么
  • 160
分享到

Go语言中的并发是什么

2024-04-02 19:04:59 160人浏览 八月长安
摘要

这篇文章主要介绍“Go语言中的并发是什么”,在日常操作中,相信很多人在Go语言中的并发是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中的并发是什么”的疑惑有所帮

这篇文章主要介绍“Go语言中的并发是什么”,在日常操作中,相信很多人在Go语言中的并发是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中的并发是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Go语言之并发

Go语言直接支持内置支持并发。当一个函数创建为goroutine时,Go会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。

Go语言运行时的调度器是一个复杂的软件,这个调度器在操作系统之上。操作系统的线程与语言运行时的逻辑处理器绑定,并在逻辑处理器上运行goroutine。

Go语言的并发同步逻辑来自一个叫做通信顺讯进程(CSP)的范型。CSP是一种消息传递模型,通过在goroutine之间传递数据来传递消息,而不是通过对数据进行加来实现同步访问。这种数据的类型叫做通道(channel) 。

并发与并行

在操作系统中,一个应用程序就可以看作一个进程,而每个进程至少包含一个线程。每个进程的初始线程被称为主线程。

操作系统会在物理处理器(CPU)上调度线程来运行,而Go语言会在逻辑处理器来调度goroutine来运行。1.5版本之上,Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器。1.5之前,默认给整个应用程序只分配一个逻辑处理器。

如下图,在运行时把goroutine调度到逻辑处理器上运行,逻辑处理器绑定到唯一的操作系统线程。

当goroutine执行了一个阻塞的系统调用(就是一个非纯CPU的任务)时,调度器会将这个线程与处理器分离,并创建一个新线程来运行这个处理器上提供的服务。

语言运行默认限制每个程序最多创建10000个线程。

注意并发≠并行!并行需要至少2个逻辑处理器。

goroutine

以并发的形式分别显示大写和小写的英文字母

package main

import (

"fmt"

"runtime"

"sync"

func main() {

// 分配一个逻辑处理器给调度器使用

runtime.GOMAXPROCS(1)

// wg用来等待程序完成

var wg sync.WaitGroup

// 计数器加2,表示要等待两个goroutine

wg.Add(2)

fmt.Println("Start!")

// 声明一个匿名函数,并创建一个goroutime

go func() {

// 通知main函数工作已经完成

defer wg.Done()

21: // 显示字母表3次

22: for count:=0; count<3;count++ {

23: for char:='a';char<'a'+26;char++ {

24: fmt.Printf("%c ", char)

25: }

26: }

27: }()

28: // 同上

29: go func() {

30: // 通知main函数工作已经完成

31: defer wg.Done()

32: // 显示字母表3次

33: for count:=0; count<3;count++ {

34: for char:='A';char<'A'+26;char++ {

35: fmt.Printf("%c ", char)

36: }

37: }

38: }()

39: // 等待goroutine结束

40: fmt.Println("Waiting!")

41: wg.Wait()

42: fmt.Println("\nFinish!")

43: }

运行结果后,可以看到先输出的是所有的大写字母,最后才是小写字母。是因为第一个goroutine完成所有显示需要花时间太短了,以至于在调度器切换到第二个goroutine之前,就完成了所有任务。

调度器为了防止某个goroutine长时间占用逻辑处理器,会停止当前正运行的goroutine,运行其他可运行的goroutine运行的机会。

创建两个相同的长时间才能完成其工作的goroutine就可以看到,比如说显示5000以内的素数值。

代码结构如下

1: go printPrime("A")

2: go printPrime("B")

3:

4: func printPrime(prefix string) {

5: ...

6: }

结果类似

1: B:2

2: B:3

3: ...

4: B:4591

5: A:3

6: A:5

7: ...

8: A:4561

9: A:4567

10: B:4603

11: B:4621

12: ...

13: // Completed B

14: A:4457

15: ...

16: // Completed A

如何修改逻辑处理器的数量

1: runtime.GOMAXPROCS(runtime.NUMCPU())

稍微改动下上面的代码,结果就会大不同

1: package main

2:

3: import (

4: "fmt"

5: "runtime"

6: "sync"

7:

8:

9: func main() {

10: // 分配两个逻辑处理器给调度器使用

11: runtime.GOMAXPROCS(2)

12: // wg用来等待程序完成

13: var wg sync.WaitGroup

14: // 计数器加2,表示要等待两个goroutine

15: wg.Add(2)

16: fmt.Println("Start!")

17: // 声明一个匿名函数,并创建一个goroutime

18: go func() {

19: // 通知main函数工作已经完成

20: defer wg.Done()

21: // 显示字母表3次

22: for count:=0; count<10;count++ {

23: for char:='a';char<'a'+26;char++ {

24: fmt.Printf("%c ", char)

25: }

26: }

27: }()

28: // 同上

29: go func() {

30: // 通知main函数工作已经完成

31: defer wg.Done()

32: // 显示字母表3次

33: for count:=0; count<10;count++ {

34: for char:='A';char<'A'+26;char++ {

35: fmt.Printf("%c ", char)

36: }

37: }

38: }()

39: // 等待goroutine结束

40: fmt.Println("Waiting!")

41: wg.Wait()

42: fmt.Println("\nFinish!")

43: }

结果类似下面的(根据CPU单核的性能结果可能结果稍微不一样)

1: Start!

2: Waiting!

3: a b c d e f g h i j k l m n o A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g

4: h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z

5: a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s

6: t u v w x y z M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X

7: Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q

8: R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

9: Finish!

可以发现,goroutine是并行运行的。

只有在有多个逻辑处理器且可以同时让每个goroutine运行在一个可用的物理处理器上的时候,goroutine才会并行运行。

竞争状态

如果两个或者多个goroutine在没有互相同步的情况下,访问某个共享的资源,并且试图同时读和写这个资源,就处于相互竞争的状态。

在竞争状态,每个goroutine都会覆盖另一个goroutine的工作。这种覆盖发生在goroutine发生切换的时候。

每个goroutien都会创造自己的共享变量副本。当切换到领另一个goroutine时,如果这个变量的值在上一个goroutine发生改变,这个goroutine再次运行时,虽然变量的值改变了,但是由于这个goroutine没有更新自己的那个副本的值,而是继续使用,并且将其存回变量的值,从而覆盖上一个goroutine 的工作。

go build –race用来竞争检测器标志来编译程序

锁住共享资源

原子函数

原子函数能够以底层的枷锁机制来同步访问整型变量和指针。省略部分代码如下:

1: var counter int64

2: go incCounter(1)

3: go incCounter(2)

4: func incCounter(id int) {

5: for count:=0;count<2;count++{

6: //安全地对counter加1

7: atomic.AddInt64(&counter, 1)

8: //当前goroutine从线程退出,并放回队列

9: runtime.Gosched()

10: }

11: }

使用atmoi包的AddInt64函数。这些goroutine都会自动根据所引用的变量做同步处理。

另外两个原子函数是LoadInt64和StoreInt64。用法如下:

1: // shutdown是通知正在执行的goroutine停止工作的标志

2: var shutdown int64

3: var wg sync.WaitGroup

4: // 该停止工作了,安全地设置shutdown标志

5: atomic.StoreInt64(&shutdown, 1)

6: // 等待goroutine结束

7: wg.Wait()

8: // 检测是否停止工作,如果shutdown==1那么goroutine就会终止

9: if atomic.LoadInt64(&shutdown) == 1 {

10: break

11: }

12:

互斥锁

另一种同步访问共享资源的方式是互斥锁。主要代码如下:

1: var (

2: // counter是所有goroutine都要增加其值的变量

3: counter int

4: wg sync.WaitGroup

5: // mutex用来定义一段代码临界区

6: mutex sync.Mutex

7: )

8: func main...

9: // 业务代码

10: func incCounter(id int) {

11: defer wg.Done()

12: for i:=0;i<2;i++ {

13: //同一时期只允许一个goroutine进入

14: mutex.Lock()

15: //大括号并不是必须的

16: {

17: //捕获counter的值

18: value := counter

19: //当前goroutine从线程退出,并返回到队列

20: runtime.Gosched()

21: //增加本地value变量的值

22: value++

23: //将该值保存回counter

24: counter = value

25: }

26: // 释放锁,允许其他正在等待的goroutine

27: mutex.Unlock()

28: }

29: }

通道

通道在goroutine之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。

可以通过通道共享内置类型,命名类型,结构类型和引用类型的值或者指针。

go语言需要使用make来创建一个通道,chan是关键字:

1: // 无缓冲的整型通道

2: unbuffered := make(chan int)

3: // 有缓冲的字符串通道

4: buffered := make(chan string, 10)

向通道发送值

1: buffered := make(chan string, 10)

2: // 通通道发送一个字符串

3: buffered <- "Gopher"

4: // 从通道接收一个字符串

5: value := <-buffered

无缓冲的通道是指在接收前没有能力保存任何值的通道。发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果没有准备好,通道会导致goroutine阻塞等待。所以无缓冲通道保证了goroutine之间同一时间进行数据交换。

1: // 四个goroutine间的接力比赛

2: package main

3:

4: import (

5: "fmt"

6: "sync"

7: "time"

8: )

9

10: var wg sync.WaitGroup

11:

12: func main()  {

13: //创建一个无缓冲的通道

14: baton := make(chan int)

15: wg.Add(1)

16: // 第一步跑步者持有接力棒

17: go Runner(baton)

18: // 开始比赛

19: baon <- 1

20: // 等待比赛结束

21: wg.Wait()

22: }

23:

24: // Ruuner模拟接力比赛中的一位跑步者

25: func Runner(baton chan int) {

26: var newRunner int

27: // 等待接力棒

28: runner := <-baton

29: // 开始跑步

30: fmt.Printf("运动员%d带着Baton跑\n", runner)

31: // 创建下一步跑步者

32: if runner != 4{

33: newRunner = runner + 1

34: fmt.Printf("运动员%d上线\n", newRunner)

35: go Runner(baton)

36: }

37: // 围绕跑到跑

38: time.Sleep(100 * time.Millisecond)

39: // 比赛结束了吗?

40: if runner == 4{

41: fmt.Printf("运动员%d完成,比赛结束\n", runner)

42: wg.Done()

43: return

44: }

45: // 将接力棒交给下一位跑步者

46: fmt.Printf("运动员%d与运动员%d交换\n", runner, newRunner)

47: baton <- newRunner

48: }

结果

1: 运动员1带着Baton跑

2: 运动员2上线

3: 运动员1与运动员2交换

4: 运动员2带着Baton跑

5: 运动员3上线

6: 运动员2与运动员3交换

7: 运动员3带着Baton跑

8: 运动员4上线

9: 运动员3与运动员4交换

10: 运动员4带着Baton跑

11: 运动员4完成,比赛结束

有缓冲的通道则能在接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。只有在通道没有可用缓冲区或者没有要接收的值时,发送或者接收才会阻塞。

1: package main

2:

3: import (

4: "fmt"

5: "math/rand"

6: "sync"

7: "time"

8: )

9:

10: const (

11: // goroutine的数量

12: numberGoroutines = 4

13: // 工作的数量

14: taskLoad = 10

15: )

16:

17: var wg sync.WaitGroup

18:

19: // 初始化随机数种子

20: func init() {

21: rand.Seed(time.Now().Unix())

22: }

23: func main() {

24: // 创建一个有缓冲的通道来管理工作

25: tasks := make(chan string, taskLoad)

26: wg.Add(numberGoroutines)

27: // 增加一组要完成的工作

28: for post:=1;post<taskLoad;post++ {

29: tasks <- fmt.Sprintf("Task:%d", post)

30: }

31: // 启动goroutine来处理工作

32: for i:=1;i<numberGoroutines+1;i++ {

33: go worker(tasks, i)

34: }

35: // 有工作处理完时关闭通道

36: closetasks)

37:

38: wg.Wait()

39: fmt.Printf("all finished!")

40:

41: }

42:

43: func worker(tasks chan string, worker_id int) {

44: defer wg.Done()

45:

46: for {

47: //等待分配工作

48: task, ok := <-tasks

49: if !ok {

50: //通道变空

51: fmt.Printf("Worker%d shut down\n", worker_id)

52: return

53: }

54: // 开始工作

55: fmt.Printf("Worker%d start %s\n", worker_id, task)

56:

57: // 随机等待一段时间

58: sleep := rand.Int63n(100)

59: time.Sleep(time.Duration(sleep)*time.Millisecond)

60: // 显示完成了工作

61: fmt.Printf("Worker%d Completed %s\n", worker_id, task)

62: }

63:

输出结果:

1: Worker4 start Task:1

2: Worker1 start Task:2

3: Worker2 start Task:3

4: Worker3 start Task:4

5: Worker3 Completed Task:4

6: Worker3 start Task:5

7: Worker4 Completed Task:1

8: Worker4 start Task:6

9: Worker2 Completed Task:3

10: Worker2 start Task:7

11: Worker3 Completed Task:5

12: Worker3 start Task:8

13: Worker Completed Task:7

14: Worker2 start Task:9

15: Worker3 Completed Task:8

16: Worker3 shut down

17: Worker4 Completed Task:6

18: Worker4 shut down

19: Worker1 Completed Task:2

20: Worker1 shut down

21: Worker2 Completed Task:9

22: Worker2 shut down

23: all inished!

由于程序和Go语言的调度器有随机的成分,结果每次都会不一样。不过总流程不会大变。

当通道关闭后,goroutine依旧从通道里的缓冲区获取数据,但是不能再向通道里发送数据。从一个已经关闭且没有数据的通道里获取数据,总会立刻返回,兵返回一个通道类型的零值。

到此,关于“Go语言中的并发是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言中的并发是什么

本文链接: https://www.lsjlt.com/news/59740.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Go语言中的并发是什么
    这篇文章主要介绍“Go语言中的并发是什么”,在日常操作中,相信很多人在Go语言中的并发是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中的并发是什么”的疑惑有所帮...
    99+
    2022-10-18
  • 为什么go语言适合并发
    本篇文章为大家展示了为什么go语言适合并发,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。操作环境:windows10系统、GO 1.11.2、thinkpad t480电脑。go语言适合并发的原因G...
    99+
    2023-06-14
  • Go语言中并发编程的最佳实践是什么?
    Go语言是一门非常受欢迎的编程语言,其并发特性是其最大的优势之一。在Go语言中,我们可以使用goroutine来实现并发编程,但是并发编程也有其一些难点和需要注意的事项。在本篇文章中,我们将介绍一些Go语言中并发编程的最佳实践。 1. 避免...
    99+
    2023-06-21
    并发 ide npm
  • go语言为什么支持高并发
    本篇内容介绍了“go语言为什么支持高并发”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!原因:go语言在设计的时候从关键字层面实现了多协程开发...
    99+
    2023-07-04
  • go语言什么时候需要并发
    go语言需要并发的情况:1、需要同时处理大量的网络请求时;2、需要处理大量的计算密集型任务时;3、需要处理实时数据流时;4、需要与外部系统进行交互时;5、需要实现高可用的系统时。本文操作环境:Windows10系统、go1.20版本、DEL...
    99+
    2023-07-17
  • 详解go语言的并发
    目录1、启动go语言的协程2、runtime.Goexit()方法。立即终止当前的协程3、runtime.GOMAXPROCS()表示go使用几个cpu执行代码4、管道定义和创...
    99+
    2022-06-07
    详解go语言 GO 并发 go语言
  • Go语言中并发的工作原理
    一、Go语言中Goroutine的基本原理 Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。 Go语言的goroutine是一个独立的工作单元, Go 语言的并发同步模型...
    99+
    2022-11-13
  • go语言开发工具是什么
    go语言开发工具是Go语言的编译器和运行时环境、Go语言的包管理工具、Go语言的集成开发环境(IDE)、Go语言的代码分析工具、Go语言的性能分析工具、Go语言的测试工具。1、Go语言的编译器和运行时环境,提供了完善的运行时环境,包括垃圾回...
    99+
    2023-07-31
  • Go语言中如何实现并发?
    Go语言作为一门新兴的编程语言,其最大的特点之一就是并发编程,它可以轻松地实现高并发的任务。那么,Go语言中如何实现并发呢?本文将为您详细解答。 goroutine goroutine是Go语言中的轻量级线程,它可以在一个单独的线程中...
    99+
    2023-09-30
    并发 shell bash
  • GO 语言中的 SHELL 是什么?
    随着计算机技术的不断发展,人们对于计算机语言的需求也越来越高。其中,Go 语言被越来越多的程序员所青睐。在 Go 语言中,SHELL 是一个非常重要的概念。本文将会介绍 SHELL 在 Go 语言中的作用以及如何使用。 一、SHELL 是...
    99+
    2023-09-05
    自然语言处理 shell 对象
  • go语言的底层是什么语言
    go语言的底层是C语言,Go语言是由C和C++开发者创建的,因此它的底层实现也是用C语言编写的,Go语言底层使用了很多C语言的特性和实现方式,如内存管理、指针、函数调用等,这使得Go语言可以直接调用C语言的库函数,也方便了Go语言与其他编程...
    99+
    2023-07-10
  • 从并发到并行解析Go语言中的sync.WaitGroup
    目录1. 理解并发与并行2. sync.WaitGroup 的作用和用法2.1 sync.WaitGroup 结构体的定义2.2 sync.WaitGroup 的方法2.3 使用示例...
    99+
    2023-05-19
    Go并发sync.WaitGroup Go sync.WaitGroup用法 Go sync.WaitGroup
  • 什么是go语言
    Go语言(又称Golang)是由Google开发的一种静态类型、编译型的开源编程语言。Go语言的设计目标是简洁、高效、安全和并发,旨在提供一种易于编写和维护的系统级编程语言。它结合了静态类型语言的安全性和高效性,以及动态类型语言的灵活性和开...
    99+
    2023-10-26
    go语言
  • Go语言是什么
    Go语言(又称 Golang)是一种静态强类型、编译型的编程语言,可以轻松构建简单、可靠以及高效的软件。Go语言语法与C语言相近,但功能更加丰富。Go语言的作者有三个,他们分别是:罗伯特·格利茨默(Robert Griesemer)、罗伯·...
    99+
    2022-10-24
  • go语言图标的发展史是什么
    这篇“go语言图标的发展史是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“go语言图标的发展史是什么”文章吧。go语言图...
    99+
    2023-07-04
  • Go语言中的并发goroutine底层原理
    目录一、基本概念①并发、并行区分②从用户态线程,内核态线程阐述go与java并发的优劣②高并发为什么是Go语言强项?③Go语言实现高并发底层GMP模型原理解析二、上代码学会Go语言并...
    99+
    2022-11-13
  • 你是否真的了解 Go 语言中的并发处理?
    Go 语言作为一门新兴的编程语言,在并发处理方面有着独特的优势。然而,很多人对于 Go 语言中的并发处理还存在着一些疑惑。本文将为大家详细介绍 Go 语言中的并发处理,并通过演示代码让大家更好地理解。 一、什么是并发处理 在计算机科学中,进...
    99+
    2023-10-20
    load 并发 面试
  • go语言是什么编程语言
    go语言是是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go是一种过程编程语言,可用于快速机器代码编译;它提供了并发机制,可以轻松开发多核和联网的机器级程序;它提供对接口和类型嵌入的支持。本教程操作环境:...
    99+
    2023-05-14
    Golang go语言
  • go语言和c语言是什么
    这篇文章主要介绍了go语言和c语言是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go语言和c语言是什么文章都会有所收获,下面我们一起来看看吧。Go(又称Golang)是Google开发的一种静态强类型、编...
    99+
    2023-07-04
  • Go语言的并发模型是如何工作的?
    Go语言是一门开源的编程语言,由Google公司开发。它因其高效的并发模型而广受欢迎。本文将深入探讨Go语言的并发模型是如何工作的,并且给出一些演示代码,帮助读者更好地理解。 一、Go语言中的并发模型 Go语言的并发模型基于Goroutin...
    99+
    2023-07-27
    同步 并发 日志
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作