iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang并发编程怎么应用
  • 625
分享到

Golang并发编程怎么应用

2023-07-06 01:07:25 625人浏览 薄情痞子
摘要

这篇文章主要讲解了“golang并发编程怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang并发编程怎么应用”吧!1、通过通信共享并发编程是一个很大的主题,这里只提供一些特定于

这篇文章主要讲解了“golang并发编程怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang并发编程怎么应用”吧!

    1、通过通信共享

    并发编程是一个很大的主题,这里只提供一些特定于go的重点内容。

    在许多环境中,实现对共享变量的正确访问所需要的微妙之处使并发编程变得困难。Go鼓励一种不同的方法,在这种方法中,共享值在通道中传递,实际上,从不由单独的执行线程主动共享。在任何给定时间,只有一个goroutine可以访问该值。根据设计,数据竞争是不可能发生的。为了鼓励这种思维方式,我们把它简化为一句口号:

    Do not communicate by sharing memory; instead, share memory by communicating.

    不要通过共享内存进行通信;相反,通过通信共享内存。

    这种方法可能走得太远。例如,引用计数最好通过在整数变量周围放置互斥来实现。但是作为一种高级方法,使用通道来控制访问可以更容易地编写清晰、正确的程序。

    考虑这个模型的一种方法是考虑一个典型的单线程程序运行在一个CPU上。它不需要同步原语。现在运行另一个这样的实例;它也不需要同步。现在让这两个程序通信;如果通信是同步器,则仍然不需要其他同步。例如,Unix管道就完美地符合这个模型。尽管Go的并发方法起源于Hoare的通信顺序处理(communication Sequential Processes, CSP),但它也可以被视为Unix管道的类型安全的泛化。

    2、Goroutines

    它们之所以被称为goroutine,是因为现有的术语——线程、协程、进程等等——传达了不准确的含义。goroutine有一个简单的模型:它是一个与相同地址空间中的其他goroutine并发执行的函数。它是轻量级的,比分配栈空间的成本高不了多少。而且栈开始时很小,所以它们很便宜,并通过根据需要分配(和释放)堆存储来增长。

    goroutine被多路复用到多个操作系统线程上,因此如果一个线程阻塞,比如在等待I/O时,其他线程继续运行。它们的设计隐藏了线程创建和管理的许多复杂性。

    在函数或方法调用前加上go关键字以在新的 goroutine 中运行该调用。当调用完成时,goroutine 将无声地退出。(效果类似于Unix shell的&符号,用于在后台运行命令。)

    go list.Sort() // run list.Sort concurrently; don't wait for it.

    function literal在goroutine调用中很方便。

    func Announce(message string, delay time.Duration) {    go func() {        time.Sleep(delay)        fmt.Println(message)    }()  // Note the parentheses - must call the function.}

    在Go中,函数字面量( function literals )是闭包: 实现确保函数引用的变量只要处于活动状态就能存活。

    3、Channels

    与map一样,通道也使用make进行分配,结果值作为对底层数据结构的引用。如果提供了可选的整数参数,它将设置通道的缓冲区大小。对于无缓冲通道或同步通道,默认值为0。

    ci := make(chan int)            // unbuffered channel of integerscj := make(chan int, 0)         // unbuffered channel of integerscs := make(chan *os.File, 100)  // buffered channel of pointers to Files

    无缓冲通道将通信(值的交换)与同步结合起来,确保两个计算(gorout例程)处于已知状态。

    有很多使用通道的好习语。这是一个开始。在前一节中,我们在后台启动了排序。通道可以允许启动goroutine等待排序完成。

    c := make(chan int)  // Allocate a channel.// Start the sort in a goroutine; when it completes, signal on the channel.go func() {    list.Sort()    c <- 1  // Send a signal; value does not matter.}()doSomethingForAWhile()<-c   // Wait for sort to finish; discard sent value.

    接收者总是阻塞,直到有数据接收。如果通道无缓冲,发送方将阻塞,直到接收方接收到该值。如果通道有缓冲区,发送方只阻塞直到值被复制到缓冲区;如果缓冲区已满,这意味着需要等待到某个接收器接收到一个值。 (参考3.1)

    有缓冲通道可以像信号量(semaphore)一样使用,例如限制吞吐量。在本例中,传入的请求被传递给handle, handle将一个值发送到通道中,处理请求,然后从通道接收一个值,以便为下一个使用者准备“信号量”。通道缓冲区的容量限制了要处理的同时调用的数量。

    var sem = make(chan int, MaxOutstanding)func handle(r *Request) {    sem <- 1    // Wait for active queue to drain.    process(r)  // May take a long time.    <-sem       // Done; enable next request to run.}func Serve(queue chan *Request) {    for {        req := <-queue        go handle(req)  // Don't wait for handle to finish.    }}

    一旦MaxOutstanding处理程序正在执行进程,试图向已充满的通道缓冲区发送的请求都将阻塞,直到现有的一个处理程序完成并从缓冲区接收。

    但是,这种设计有一个问题:Serve为每个传入的请求创建一个新的goroutine ,尽管在任何时候, 只有MaxOutstanding多个可以运行。因此,如果请求来得太快,程序可能会消耗无限的资源。我们可以通过更改Serve来限制goroutines的创建来解决这个缺陷。这里有一个明显的解决方案,但要注意它有一个bug,我们随后会修复:

    func Serve(queue chan *Request) {    for req := range queue {        sem <- 1        go func() {            process(req) // Buggy; see explanation below.            <-sem        }()    }}

    bug 在于,在Go for循环中,循环变量在每次迭代中都被重用,因此req变量在所有goroutine中共享。这不是我们想要的。我们需要确保每个goroutine的req是唯一的。这里有一种方法,在goroutine中将req的值作为参数传递给闭包:

    func Serve(queue chan *Request) {    for req := range queue {        sem <- 1        go func(req *Request) {            process(req)            <-sem        }(req)    }}

    将此版本与前一个版本进行比较,查看闭包的声明和运行方式的差异。另一个解决方案是创建一个同名的新变量,如下例所示:

    func Serve(queue chan *Request) {    for req := range queue {        req := req // Create new instance of req for the goroutine.        sem <- 1        go func() {            process(req)            <-sem        }()    }}

    这样写似乎有些奇怪

    req := req

    但在Go 中这样做是合法的和惯用的。您将得到一个具有相同名称的新变量,故意在局部掩盖循环变量,但对每个goroutine都是惟一的。

    回到编写服务器的一般问题,另一种很好地管理资源的方法是启动固定数量的handle goroutines ,所有这些handle goroutines 都从请求通道读取。goroutine的数量限制了process同时调用的数量。这个Serve函数还接受一个通道,它将被告知退出该通道;在启动goroutines之后,它会阻止从该通道接收。

    func handle(queue chan *Request) {    for r := range queue {        process(r)    }}func Serve(clientRequests chan *Request, quit chan bool) {    // Start handlers    for i := 0; i < MaxOutstanding; i++ {        go handle(clientRequests)    }    <-quit  // Wait to be told to exit.}

    3.1 Channel都有哪些特性

    Go语言中的channel具有以下几个特性:

    线程安全

    channel是线程安全的,多个协程可以同时读写一个channel,而不会发生数据竞争的问题。这是因为Go语言中的channel内部实现了机制,保证了多个协程之间对channel的访问是安全的。

    阻塞式发送和接收

    当一个协程向一个channel发送数据时,如果channel已经满了,发送操作会被阻塞,直到有其他协程从channel中取走了数据。同样地,当一个协程从一个channel中接收数据时,如果channel中没有数据可供接收,接收操作会被阻塞,直到有其他协程向channel中发送了数据。这种阻塞式的机制可以保证协程之间的同步和通信。

    顺序性

    通过channel发送的数据是按照发送的顺序进行排列的。也就是说,如果协程A先向channel中发送了数据x,而协程B再向channel中发送了数据y,那么从channel中接收数据时,先接收到的一定是x,后接收到的一定是y。

    可以关闭

    通过关闭channel可以通知其他协程这个channel已经不再使用了。关闭一个channel之后,其他协程仍然可以从中接收数据,但是不能再向其中发送数据了。关闭channel的操作可以避免内存泄漏等问题。

    缓冲区大小

    channel可以带有一个缓冲区,用于存储一定量的数据。如果缓冲区已经满了,发送操作会被阻塞,直到有其他协程从channel中取走了数据;如果缓冲区已经空了,接收操作会被阻塞,直到有其他协程向channel中发送了数据。缓冲区的大小可以在创建channel时指定,例如:

    ch := make(chan int, 10)

    会panic的几种情况

    向已经关闭的channel发送数据

    关闭已经关闭的channel

    关闭未初始化的nil channel

    会阻塞的情况:

    从未初始化 nil channel中读数据

    向未初始化 nil channel中发数据

    在没有读取的groutine时,向无缓冲channel发数据,

    有缓冲区,但缓冲区已满,发送数据时

    在没有数据时,从无缓冲或者有缓冲channel读数据

    返回零值:

    从已经关闭的channe接收数据

    3.2 channel 的最佳实践

    在使用channel时,应该遵循以下几个最佳实践:

    避免死锁

    使用channel时应该注意避免死锁的问题。如果一个协程向一个channel发送数据,但是没有其他协程从channel中取走数据,那么发送操作就会一直被阻塞,从而导致死锁。为了避免这种情况,可以使用select语句来同时监听多个channel,从而避免阻塞。

    避免泄漏

    在使用channel时应该注意避免内存泄漏的问题。如果一个channel没有被关闭,而不再使用了,那么其中的数据就无法被释放,从而导致内存泄漏。为了避免这种情况,可以在协程结束时关闭channel。

    避免竞争

    在使用channel时应该注意避免数据竞争的问题。如果多个协程同时读写一个channel,那么就可能会发生竞争条件,从而导致数据不一致的问题。为了避免这种情况,可以使用锁机制或者使用单向channel来限制协程的访问权限。

    避免过度使用

    在使用channel时应该注意避免过度使用的问题。如果一个程序中使用了大量的channel,那么就可能会导致程序的性能下降。为了避免这种情况,可以使用其他的并发编程机制,例如锁、条件变量等。

    4、Channels of channels

    Go最重要的属性之一是通道是first-class值,可以像其他值一样分配和传递。此属性的常见用途是实现安全的并行多路解复用。

    在上一节的示例中,handle是请求的理想处理程序,但我们没有定义它处理的类型。如果该类型包含要在其上回复的通道,则每个客户机都可以为应答提供自己的路径。下面是Request类型的示意图定义。

    type Request struct {    args        []int    f           func([]int) int    resultChan  chan int}

    客户端提供了一个函数及其参数,以及请求对象内用于接收answer的通道。

    func sum(a []int) (s int) {    for _, v := range a {        s += v    }    return}request := &Request{[]int{3, 4, 5}, sum, make(chan int)}// Send requestclientRequests <- request// Wait for response.fmt.Printf("answer: %d\n", <-request.resultChan)

    服务器端,唯一需要更改的是处理程序函数。

    func handle(queue chan *Request) {    for req := range queue {        req.resultChan <- req.f(req.args)    }}

    显然,要实现它还有很多工作要做,但这段代码是一个速率受限、并行、非阻塞rpc系统的框架,而且还没有看到mutex 。

    5、并行(Parallelization)

    这些思想的另一个应用是跨多个CPU核并行计算。如果计算可以被分解成可以独立执行的独立部分,那么它就可以被并行化,并在每个部分完成时用一个通道发出信号。

    假设我们有一个昂贵的操作要对一个items的向量执行,并且每个item的操作值是独立的,就像在这个理想的例子中一样。

    type Vector []float64// Apply the operation to v[i], v[i+1] ... up to v[n-1].func (v Vector) DoSome(i, n int, u Vector, c chan int) {    for ; i < n; i++ {        v[i] += u.Op(v[i])    }    c <- 1    // signal that this piece is done}

    我们在一个循环中独立地启动这些片段,每个CPU一个。它们可以按任何顺序完成,但这没有关系;我们只是在启动所有的goroutine之后通过排泄通道来计算完成信号。

    const numCPU = 4 // number of CPU coresfunc (v Vector) DoAll(u Vector) {    c := make(chan int, numCPU)  // Buffering optional but sensible.    for i := 0; i < numCPU; i++ {        go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c)    }    // Drain the channel.    for i := 0; i < numCPU; i++ {        <-c    // wait for one task to complete    }    // All done.}

    我们不需要为numCPU创建一个常量,而是可以询问运行时哪个值是合适的。函数runtime.NumCPU返回机器中硬件CPU核数,因此我们可以这样写

    还有一个函数 runtime.GOMAXPROCS,它报告(或设置)用户指定的Go程序可以同时运行的核数。默认值为runtime.NumCPU,但可以通过设置类似命名的shell环境变量或调用带有正数的函数来覆盖。用0调用它只是查询值。因此,如果我们想要满足用户的资源请求,我们应该写

    var numCPU = runtime.GOMAXPROCS(0)

    请务必不要混淆并发性(concurrency,将程序构造为独立执行的组件)和并行性(parallelism, 在多个cpu上并行执行计算以提高效率)这两个概念。尽管Go的并发特性可以使一些问题很容易构建为并行计算,但Go是一种并发语言,而不是并行语言,并且并不是所有的并行化问题都适合Go的模型。关于区别的讨论,请参阅本文章中引用的谈话。

    6、漏桶缓冲区(A leaky buffer)

    并发编程的工具甚至可以使非并发的想法更容易表达。下面是一个从RPC包中抽象出来的示例。客户端goroutine循环从某个源(可能是网络)接收数据。为了避免分配和释放缓冲区,它保留了一个空闲列表,并使用缓冲通道来表示它。如果通道为空,则分配一个新的缓冲区。一旦消息缓冲区准备好了,它就被发送到serverChan上的服务器。

    var freeList = make(chan *Buffer, 100)var serverChan = make(chan *Buffer)func client() {    for {        var b *Buffer        // Grab a buffer if available; allocate if not.        select {        case b = <-freeList:            // Got one; nothing more to do.        default:            // None free, so allocate a new one.            b = new(Buffer)        }        load(b)              // Read next message from the net.        serverChan <- b      // Send to server.    }}

    服务器循环从客户端接收每条消息,处理它,并将缓冲区返回到空闲列表。

    func server() {    for {        b := <-serverChan    // Wait for work.        process(b)        // Reuse buffer if there's room.        select {        case freeList <- b:            // Buffer on free list; nothing more to do.        default:            // Free list full, just carry on.        }    }}

    客户端尝试从freeList中检索缓冲区;如果没有可用的,则分配一个新的。服务器发送给freeList的消息会将b放回空闲列表中,除非空闲列表已满,在这种情况下,缓冲区将被丢弃在地板上,由垃圾收集器回收。(当没有其他case可用时,select语句中的default 子句将执行,这意味着select语句永远不会阻塞。)此实现仅用几行就构建了一个漏桶列表,依赖于缓冲通道和垃圾收集器进行记账。

    感谢各位的阅读,以上就是“Golang并发编程怎么应用”的内容了,经过本文的学习后,相信大家对Golang并发编程怎么应用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

    您可能感兴趣的文档:

    --结束END--

    本文标题: Golang并发编程怎么应用

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

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

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

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

    下载Word文档
    猜你喜欢
    • Golang并发编程怎么应用
      这篇文章主要讲解了“Golang并发编程怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang并发编程怎么应用”吧!1、通过通信共享并发编程是一个很大的主题,这里只提供一些特定于...
      99+
      2023-07-06
    • Golang中的并发编程与WaitGroup的结合应用
      在Golang中,可以使用WaitGroup来管理并发的goroutine,以确保在所有goroutine完成之前,主程序不会退出。...
      99+
      2023-10-08
      Golang
    • golang函数闭包在并发编程中的应用
      闭包是 go 中允许函数访问外部变量的特性,在并发编程中很有用。通过闭包,协程可以安全共享数据和传值。闭包在并发编程中的常见应用包括:共享数据,无需同步机制。协程之间传值,即使值在闭包闭...
      99+
      2024-04-23
      闭包 并发编程 golang 作用域 同步机制
    • golang函数与goroutine在并发编程中的应用
      go 语言中的函数与 goroutine 可用于并发编程。函数是线程安全的代码块,可同时被多个 goroutine 调用。goroutine 是轻量级线程,在用户空间运行,并可共享内存。...
      99+
      2024-04-25
      并发编程 golang
    • Java并发编程之StampedLock锁怎么应用
      本篇内容介绍了“Java并发编程之StampedLock锁怎么应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!StampedLock:St...
      99+
      2023-06-30
    • 并发编程中Golang协程和线程的差异及应用
      Golang协程与线程的区别及其在并发编程中的应用 引言:在并发编程领域,Golang以其卓越的效率和简洁性受到了广泛的关注。Golang通过协程(Goroutine)和信道(Channel)的机制实现了高效...
      99+
      2024-01-24
    • Golang Facade模式在并发编程中的应用探索
      在并发编程中,Golang的Facade模式可以用于隐藏底层复杂的并发逻辑,提供一个简单的接口供其他线程或协程使用。在Golang中...
      99+
      2023-10-08
      Golang
    • Python爬虫中的并发编程怎么应用
      什么是并发编程并发编程是指在一个时间段内,能够执行多个操作的程序设计,通常表现为程序中有多个任务同时启动,可以运行并且相互之间不会产生影响。并发编程的好处是可以提高程序的性能和响应能力。并发编程在爬虫中的应用爬虫程序是典型的 I/O 密集型...
      99+
      2023-05-14
      Python
    • Golang并发编程之GMP模型怎么实现
      本文小编为大家详细介绍“Golang并发编程之GMP模型怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang并发编程之GMP模型怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。0. 简介传统...
      99+
      2023-07-05
    • Golang函数如何用于并发编程?
      go 语言通过 goroutine 实现并发编程,它允许同时执行轻量级线程。goroutine 可以使用 go 关键字创建,并通过管道(chan 类型)进行通信。实战案例中,我们并行查找...
      99+
      2024-04-12
      并发编程 golang函数 golang
    • Golang函数并发编程在大型项目中的应用
      大型 go 项目中,并发编程可提升性能和可伸缩性。1. 并发原始值:goroutine 为轻量级线程,channel 为安全传递数据的缓冲区。2. 并发模式:管道并发用于生产者消费者模型...
      99+
      2024-04-17
      并发编程 大型项目 mysql golang
    • Golang并发编程重点讲解
      目录1、通过通信共享2、Goroutines3、Channels3.1 Channel都有哪些特性3.2 channel 的最佳实践4、Channels of channels5、并...
      99+
      2023-05-15
      Go并发机制 Go并发编程
    • 如何利用Golang进行并发编程
      在现代计算机系统中,随着计算机科学和技术不断创新,多线程和并发已成为当今软件开发中最流行的主题之一。多线程和并发旨在有效利用计算机系统的资源以提高计算效率和性能。在并发编程领域中,Golang是一种强大而流行的程序设计语言,它具有优异的内置...
      99+
      2023-05-14
    • Golang并发编程之Channel详解
      目录0. 简介1. channel数据结构2. channel创建3. 数据发送3.1 空通道的数据发送3.2 直接发送3.3 缓存区3.4 阻塞发送4. 接收数据4.1 空通道的数...
      99+
      2023-05-19
      Golang并发编程Channel Golang并发编程 Golang Channel
    • Golang并发编程深入分析
      目录Go 协程和普通线程对比内核级线程(线程)线程优点线程缺点用户级线程(协程)协程优点协程缺点调度器(GPM)Go 使用协程创建协程注意Go 协程和普通线程对比 Go 拥有极强的并...
      99+
      2022-11-21
      Golang 并发编程 Go 并发模型
    • 使用Golang进行并发编程的示例
      这篇文章给大家分享的是有关使用Golang进行并发编程的示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Golang最擅长的就是并发编程,使用Golang可以很方便的进行并发编程。先看一段普通的代码packag...
      99+
      2023-06-14
    • Golang 进程管理:探寻并发编程
      go 语言的并发原语提供了 goroutine、channel、同步等机制,用于构建和管理并发进程。实践中,这些原语可用于创建多线程处理请求的 web 服务器,以提高吞吐量和响应时间。 ...
      99+
      2024-04-04
      并发编程 进程管理 golang
    • Python并发编程:如何应对Apache高并发?
      Apache作为一款世界知名的Web服务器,因其稳定性、安全性、可靠性等特点,在众多Web开发中得到广泛应用。然而,随着互联网的快速发展,用户对Web应用的访问量越来越大,Apache面临着越来越大的高并发压力。如何通过Python并发编...
      99+
      2023-11-13
      并发 apache 关键字
    • Go并发编程sync.Cond怎么使用
      本篇内容主要讲解“Go并发编程sync.Cond怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go并发编程sync.Cond怎么使用”吧!简介Go 标准库提供 Cond 原语的目的是,为...
      99+
      2023-06-30
    • 利用Golang函数利用并发编程的技巧
      利用 golang 实现并发编程:创建 goroutine:使用 go 关键字创建轻量级线程 goroutine。使用通道:通道是 goroutine 间通信的数据结构,可发送和接收值。...
      99+
      2024-04-12
      golang
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作