golang并发编程基础

简介: 并发基础

go并发编程

1waitgroup

WaitGroup就是等待所有的goroutine全部执行完毕,add方式和Down方法要配套使用

package main
​
import (
    "fmt"
    "sync"
)
​
func main()  {
    var wq sync.WaitGroup
​
    wq.Add(100) //监控多少个goroutine执行结束
​
    for i:= 0;i<100;i++ {
        //开启一个协程
        go func(i int) {
            defer wq.Done() //和add是一起使用的
            fmt.Println(i)
        }(i)
    }
​
    wq.Wait() //等待所有的goroutine结束
}
​

2通过锁来完成全局变量的原子性操作

开启两个gorountine对total进行相同此时的加减,但是这一段程序的运行结果每一次都不一样

资源竞争,加锁

package main
​
import (
    "fmt"
    "sync"
)
​
var total int
var wg sync.WaitGroup
​
func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(total)
}
func add() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
        total += 1
    }
}
​
func sub() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
        total -= 1
    }
}
​

加锁之后代码成功运行

package main
​
import (
    "fmt"
    "sync"
)
​
var total int
var wg sync.WaitGroup
var lock sync.Mutex
​
func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(total)
}
func add() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
        lock.Lock()
        total += 1
        lock.Unlock()
    }
}
​
func sub() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
        lock.Lock()
        total -= 1
        lock.Unlock()
    }
}
​

锁不能复制。

更加优雅的方式,使用golang的原子包

package main
​
import (
    "fmt"
    "sync"
    "sync/atomic"
)
​
var total int64
var wg sync.WaitGroup
​
//var lock sync.Mutex
​
func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(total)
}
func add() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
​
        atomic.AddInt64(&total, 1)  //原子性的操作
​
    }
}
​
func sub() {
    defer wg.Done()
    for i := 0; i < 100000; i++ {
        atomic.AddInt64(&total,-1)
    }
}
​

lock相对atomic性能较差,lock基于操作系统调度

3读写锁

锁实际上是将并行的代码串行化了,使用lock肯定影响性能,即使是设计所,也应该尽量保证并行

4goroutine进行通讯

不要通过共享内存来通讯,而要通过通讯来实现内存共享

channel的基础用法

package main
​
import "fmt"
​
func main()  {
    // 名字  类型  存储类型
    var msg chan string //默认值未nil
​
    msg = make(chan string ,1)  //channel的初始化的值如果是0的话,放值进去会阻塞,如果设为0就为无缓冲channel
​
    msg <- "大大怪" //将右边的值放在channel中
    name := <- msg //将channel中的值取出来给name
    fmt.Println(name)
​
}
​
​

无缓冲channel用法

package main
​
import (
    "fmt"
    "time"
)
​
func main() {
    // 名字  类型  存储类型
    var msg chan string //默认值未nil
​
    msg = make(chan string, 0) //channel的初始化的值如果是0的话,放值进去会阻塞,如果设为0就为无缓冲channel
    go func(msg chan string) {
        name := <-msg //将channel中的值取出来给name
        fmt.Println(name)
    }(msg)
    msg <- "大大怪"  //将右边的值放在channel中
​
time.Sleep(time.Second*10)
​
}
​

waitgroup少了一个done容易出现deadlock

无缓冲的channel也容易出现deadlock

适用场景

无缓冲channel适用于通知B要第一时间知道A是否已经完成

有缓冲channel适用于生产者和消费者之间的通讯

go中channel的应用场景

  • 消息传递,消息过滤
  • 信号广播
  • 事件订阅和广播
  • 任务分发
  • 结果汇总
  • 并发控制
  • 同步和异步

5.单项channel的使用

默认情况下,channel是双向的,但是我们经常一个channel作为参数进行传递,希望对象也是单向使用

package main
​
import "fmt"
​
func main() {
    //var ch1 chan int //双向的channel
    //var ch2 chan<- float64 //单项channel,只能写入float64的数据
    //var ch3 <-chan int //只能读取int类型的数据
​
    /*
    定义一个channel然后把它编程单向的,但是不能把单项的channel转成双向的channel
     */
    c := make(chan int, 3)
    var send chan<- int = c
    var read <-chan int = c
​
    send <- 1
    num := <- read
​
    fmt.Println(num)
}

模拟单项channel存取数据

package main
​
import (
    "fmt"
    "time"
)
​
func producer(out chan<- int)  {
    for i:=0;i<10 ;i++  {
        out <- i*i
    }
    close(out)
}
​
func consumer(in <-chan int)  {
    for num := range in {
        fmt.Println(num)
    }
}
func main() {
    /*
    内部会完成自动的类型转换
     */
    c := make(chan int)
    go producer(c)
    go consumer(c)
    time.Sleep(10*time.Second)
}
​

6交替打印

这是一道经典题目,在Java中也有提到,交替打印这个序列

12ab34cd56ef78gh910ij1112kl1314mn1516op1718qr1920st2122uv2324wx2526yz2728

利用channel阻塞的特性来实现

package main
​
import (
    "fmt"
    "time"
)
​
var number, letter = make(chan bool), make(chan bool)
​
func printNum() {
    i := 1
    for {
        //等待另外一个goroutine进行通知
        <-number //从number进行取值的操作,如果没有值就阻塞
        fmt.Printf("%d%d", i, i+1)
        i += 2
        letter <- true
    }
}
func printLetter() {
    str := "abcdefghijklmnopqrstuvwxyz"
    i := 0
    for {
        <-letter
        if i>= len(str){
            return
        }
            fmt.Print(str[i : i+2])
        i += 2
        number <- true // 存入true到channel中
    }
}
​
func main() {
    go printNum()
    go printLetter()
    number <- true
    time.Sleep(10*time.Second)
}
​
相关文章
|
安全 Go
Golang 语言使用 channel 并发编程
Golang 语言使用 channel 并发编程
51 0
|
6月前
|
程序员 Go
Golang深入浅出之-Select语句在Go并发编程中的应用
【4月更文挑战第23天】Go语言中的`select`语句是并发编程的关键,用于协调多个通道的读写。它会阻塞直到某个通道操作可行,执行对应的代码块。常见问题包括忘记初始化通道、死锁和忽视`default`分支。要解决这些问题,需确保通道初始化、避免死锁并添加`default`分支以处理无数据可用的情况。理解并妥善处理这些问题能帮助编写更高效、健壮的并发程序。结合使用`context.Context`和定时器等工具,可提升`select`的灵活性和可控性。
104 2
|
6月前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
89 1
|
并行计算 安全 Go
Golang并发编程技术:解锁Go语言的并行潜力
本文将介绍Golang中的一些关键特性和技术,以帮助您更好地理解和利用Go语言的并发编程潜力。
96 0
|
并行计算 安全 Go
Golang协程:并发编程的利器
在Go编程语言中,协程(goroutine)是一种轻量级的线程,用于实现并发编程。与传统的线程相比,协程更加高效、简洁,并且易于使用。本篇博客将深入探讨Golang中的协程及其优势。
118 0
|
Go 调度 开发者
并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13
如果说Go lang是静态语言中的皇冠,那么,Goroutine就是并发编程方式中的钻石。Goroutine是Go语言设计体系中最核心的精华,它非常轻量,一个 Goroutine 只占几 KB,并且这几 KB 就足够 Goroutine 运行完,这就能在有限的内存空间内支持大量 Goroutine协程任务,方寸之间,运筹帷幄,用极少的成本获取最高的效率,支持了更多的并发,毫无疑问,Goroutine是比Python的协程原理事件循环更高级的并发异步编程方式。
并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13
|
存储 Go
Golang并发编程
Golang并发编程
160 0
Golang并发编程
|
安全 算法 编译器
Golang并发编程控制
Golang并发编程控制
205 0
Golang并发编程控制
|
数据采集 安全 算法
深入学习 Golang 之并发编程
详细介绍了 Golang 并发编程相关知识。
184 0
深入学习 Golang 之并发编程
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
111 4
Golang语言之管道channel快速入门篇