Go语言并发编程

1.多线程

  • 1.线程是由操作系统进行管理,也就是处于内核态
  • 2.线程之间进行切换,需要发生用户态到内核态的切换
  • 3.当系统中运行大量线程,系统就会变得非常慢
  • 4.用户态的线程,支持大量线程创建,也叫协程或goroutine

2.创建goroutine

1.使用go这个关键字创建一个新的轻量级线程(也叫协程)

package main

import "fmt"

func hello()  {
    fmt.Println("hello world")
}

func main()  {
    // 开启新的线程去执行hello()
    go hello()
    fmt.Println("This is a test")
}
  • go语言中,主线程退出后,子线程不会继续执行,也就是没有守护线程

2.Goroutine原理浅析

  • 一个操作系统线程对应用户态多个goroutine
  • 同时使用多个操做系统线程
  • 操做系统线程对goroutine是多对多关系,即M:N


3.多核控制

1.通过runtime包进行多核设置
2.GOMAXPROCS设置当前程序运行时占用的cpu核数
3.NumCPU获取当前系统的CPU核数
package main

import (
    "fmt"
    "runtime"
    "time"
)

var j int
func hello()  {
    for {
	j++
    }
}

func main()  {
    cpu := runtime.NumCPU()
    fmt.Println("cup num: ", cpu)  // 获取cpu物理核数

    runtime.GOMAXPROCS()  // 当前程序不管开启多少个协程,只占用一核的CPU

    for i := 0; i < 10; i++ {
	go hello()
    }

    time.Sleep(time.Hour)
}

4.channel介绍

1.概念
  • 1.本质上就是一个队列,是一个容器
  • 2.因此定义的时候,需要指定容器中元素的类型
  • 3.示例:var nums chan int
2.操做
  • 1.定义
// 声明一个整形队列,c此时是一个nil对象
var c chan int

// 初始化,容量给1000
c = make(chan int, 1000)
  • 2.元素出队核入队
// 入队
c <- 100

// 出队(可以使用for range)
data := <- c
  • 3.获取队列存出的元素个数
fmt.Println(len(c))
  • 4.获取队列容量
fmt.Println(cap(c))
3.实例
package main

import "fmt"

func main()  {
    // 声明一个整形队列,c此时是一个nil对象
    var c chan int
    // 初始化,容量给1000
    c = make(chan int, 3)

    c <- 10
    c <- 9
    c <- 8

    data := <- c
    fmt.Println(data)
}
4.阻塞chan
package main

import (
    "fmt"
    "time"
)

func test(flag chan bool)  {
    for i := 0; i < 10; i++ {
	fmt.Println(i)
	time.Sleep(time.Second)
    }
    flag <- true
}
func main()  {
    var flag chan bool
    flag = make(chan bool)
    go test(flag)
    <-flag
}
4.提示
  • 1.如果初始化空队列,即不指定容量,此时无法直接插入元素,只有当有程序从队列往外取值时,才可以插入
  • 2.如果队列中没有元素,取值时会阻塞等待,直到取到元素后继续往下执行

5.单向channel介绍

1.只写队列

chan<- int

2.只读队列

<- chan

3.关闭队列

close(xxx)

4.判断队列是否关闭

data, ok := <- chan
if !ok {
  break
}

5.队列遍历

for v := range nums1 {
    println(v)
} 

6.实例

package main

import (
    "fmt"
)


func main()  {
    var nums1 <-chan int
    nums1 = make(<- chan int, 10)
    //nums1 <- 1  // Invalid operation: nums1 <- 1 (send to receive-only type <-chan int)
    data , ok := <- nums1
    if !ok {
	fmt.Println("队列已关闭")
    }
    fmt.Println(data)
}

6.Waitgroup

1.如何等待一组groutine结束

  • 1.使用不带缓冲区的channel实现
package main

import (
    "fmt"
    "time"
)

func process(i int, flag chan bool)  {
    fmt.Println("start goroutine: ", i)
    time.Sleep(time.Second)
    fmt.Printf("goroutine %d ended\n", i)
    flag <- true
}

func main()  {
    flag := make(chan bool, 3)
    for i := 0; i < 3; i++ {
	go process(i, flag)
    }

    for j := 0; j < 3; j++ {
	<- flag
    }

    fmt.Println("All go routines finished executing")
}

/*
start goroutine:  2
start goroutine:  1
start goroutine:  0
goroutine 1 ended
goroutine 0 ended
goroutine 2 ended
All go routines finished executing
*/
  • 2.使用sync.WaitGroup实现
package main

import (
    "fmt"
    "sync"
    "time"
)

func process(i int, wg *sync.WaitGroup)  {
    fmt.Println("start goroutine: ", i)
    time.Sleep(time.Second)
    fmt.Printf("goroutine %d ended\n", i)
    wg.Done()  // 计数-1
}

func main()  {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
	wg.Add(1)  // 计数+1
	go process(i, &wg)
    }
    wg.Wait()  // 计数=0时结束
    fmt.Println("All go routines finished executing")
}

7.Workerpool的实现

1.worker池的实现

  • 1.生产者、消费者模型、简单有效
  • 2.控制goroutine的数量,防止goroutine泄露和暴涨
  • 3.基于goroutinechan,构建workerpool非常简单

2.项目需求分析

  • 计算一个数字的各个位数之和,例如:123,和等于1+2+3=6
  • 需要计算的数字使用随机算法生成

3.方案介绍

4.编码实现

package main

import (
    "fmt"
    "math/rand"
)

type Job struct {
    Number int
    Id int
}

type Result struct {
    job Job
    sum int
}

func Add(job *Job, result chan *Result)  {
    sum := 0
    number := job.Number
    for number != 0{
	tmp := number % 10  // 对10取余,得到个位数
	sum += tmp
	number /= 10  // 除10取整,抛弃个位数
    }
    r := &Result{
	job: *job,
	sum: sum,
    }

    result <- r
}

func Worker(jobChan chan *Job, resultChan chan *Result)  {
    for job := range jobChan {
	Add(job, resultChan)
    }
}

func startWorker(num int, jobChan chan *Job, resultChan chan *Result)  {
    for i := 0; i < num; i++ {
	go Worker(jobChan, resultChan)
    }
}

func printResult(resultChan chan *Result)  {
    for result := range resultChan {
	fmt.Printf("job id: %d number: %v result: %d\n", result.job.Id, result.job.Number, result.sum)
    }
}
func main()  {
    jobChan := make(chan *Job, 1000)
    resultChan := make(chan *Result, 1000)

    // worker池创建,池中每个worker都从job队列中取数据,然后取计算,计算结果插入result结果队列
    startWorker(128, jobChan, resultChan)

    // 单独线程从结果池中取
    go printResult(resultChan)

    // 主线程生产job任务
    id := 1
    for {
        // 生成随机数
	number := rand.Int()
	job := &Job{
	    Id: id,
	    Number: number,
	}
	jobChan <- job
	id += 1
    }
}
posted @ 2022-09-27 14:26  fatpuffer  阅读(107)  评论(0)    收藏  举报