并发,并行,串行

部分参考节选自CSDN博客:

通俗理解:
吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)

吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

普通解释:

  • 并发:交替做不同事情的能力

  • 并行:同时做不同事情的能力

专业术语:

  • 并发:不同的代码块交替执行

  • 并行:不同的代码块同时执行

并发

部分转载自51CTO博客:
部分参考自知乎:

并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。

并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

  • 并发是一种现象:同时运行多个程序或多个任务需要被处理的现象

  • 这些任务可能是并行执行的,也可能是串行执行的,和CPU核心数无关,是操作系统进程调度和CPU上下文切换达到的结果

  • 解决大并发的一个思路是将大任务分解成多个小任务:

    • 可能要使用一些数据结构来避免切分成多个小任务带来的问题

    • 可以多进程/多线程并行的方式去执行这些小任务达到高效率

    • 或者以单进程/单线程配合多路复用执行这些小任务来达到高效率

并行

部分转载自博客:

并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

<注意>:
– 如果是单进程/单线程的并行,那么效率比串行更差

  • 如果只有单核cpu,多进程并行并没有提高效率

  • 从任务队列上看,由于同时从队列中取得多个任务并执行,相当于将一个长任务队列变成了短队列

串行

一次只能取得一个任务并执行这一个任务

协程池DEMO

框架图

代码

package main

import (
    "fmt"
    "time"
)

type Task struct {
    f func() error
}

// 创建一个任务
func NewTask(f func() error) *Task {
    return &Task{f: f}
}

//  执行任务
func (t *Task) Excute() {
    t.f()
}

// ------------------Go routine------------------
// Language: go
type Pool struct {
    // 对外提供的接口
    EntryChannel chan *Task
    // 内部Task队列
    JobsChannel chan *Task
    // 协程池最大worker数量
    workerNum int
}

// 创建一个协程池
func NewPool(worker int) *Pool {
    return &Pool{
        EntryChannel: make(chan *Task),
        JobsChannel:  make(chan *Task),
        workerNum:    worker,
    }
}

//
func (p *Pool) Worker(i int) {
    for i := 0; i < p.workerNum; i++ {
        go func() {
            for {
                select {
                case job := <-p.JobsChannel:
                    job.Excute()
                    fmt.Println("workerID", i, "excute task finished")
                }
            }
        }()
    }
    // for task := range p.JobsChannel {
    //  // 一旦任务队列有任务,就执行任务
    //  task.Excute()
    //  fmt.Println("workerID", i, "excute task finished")
    // }
}

// ------------------协程池工作------------------
func (p *Pool) Run() {
    // 根据worker数量创建worker
    // 从EntryChannel中获取任务, 并将任务放入JobsChannel
    for i := 0; i < p.workerNum; i++ {
        // 每一个worker都应该是一个go routine
        go p.Worker(i)
    }

    for task := range p.EntryChannel {
        // 如果读取到task,就将task放入JobsChannel
        p.JobsChannel <- task
    }
}

func main() {
    // 创建一个任务
    t1 := NewTask(task01)
    t2 := NewTask(task02)
    // 创建一个协程池
    pool := NewPool(10)
    totalNum := 0
    go func() {
        for {
            pool.EntryChannel <- t1
            pool.EntryChannel <- t2
            totalNum += 1
            fmt.Println("totalNum:", totalNum)
        }
    }()
    pool.Run()
}

func task01() error {
    fmt.Println("task01", time.Now().Unix())
    return nil
}
func task02() error {
    fmt.Println("task02", time.Now().Unix())
    return nil
}

channel通信机制复盘

下面这样会报错,因为·count退出后,c就不能再使用了,main永远接收不到来自channel的数据了,造成死锁

// 下面这样会报错,因为count退出后,c就不能再使用了,main永远接收不到来自channel的数据了,造成死锁
func main() {
    c := make(chan string)
    go count(5, "羊", c)
    for {
        message := <-c
        fmt.Println(message)
    }
}

func count(n int, s string, c chan string) {
    for i := 0; i < n; i++ {
        c <- s
        time.Sleep(time.Millisecond * 500)
    }
    // close(c)
}

/*
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        D:/Go/learn/goPool/routine.go:13 +0xba
exit status 2
*/

改进呢很简单

// 稍微复杂写法
func main() {
    c := make(chan string)
    go count(5, "羊", c)
    for {
        message, open := <-c
        // 加了open的判断,就可以避免死锁,判断channel是否关闭,
        if !open {
            break
        }
        fmt.Println(message)
    }
}

// 这样写更为简单
func main2() {
    c := make(chan string)
    go count(5, "羊", c)
    for message := range c {
        fmt.Println(message)
    }
}

func count(n int, s string, c chan string) {
    for i := 0; i < n; i++ {
        c <- s
        time.Sleep(time.Millisecond * 500)
    }
    // 在执行完后主动关闭channel,
    close(c)
}

/*
PS D:\Go\learn\goPool> go run .\routine02Change.go
羊
羊
羊
羊
羊
PS D:\Go\learn\goPool>
*/

并发应用场景(搜索文件)

未使用并发

var (
    matches int
    query   string
)

func main() {
    start := time.Now()
    query = "main.go"
    search("d:/", true)
    fmt.Println(matches, "matches found")
    fmt.Println(time.Since(start))
}

func search(path string, master bool) {
    files, err := ioutil.ReadDir(path)
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, file := range files {
        name := file.Name()
        if name == query {
            matches++
            fmt.Println(path + "/" + name)
        }
        if file.IsDir() {
            fmt.Println("search in", path+"/"+name)
            // 如果worker数量超过了最大值,就直接递归搜索
            search(path+"/"+name, true)
        }
    }
}

使用并发

var (
    matches        int
    query          string
    workerCount    int
    maxWorkerCount int = 12
    searchRequest      = make(chan string)
    workerDone         = make(chan bool)
    foundMatch         = make(chan bool)
)

func main() {
    start := time.Now()
    query = "main.go"
    workerCount = 1
    go search("d:/", true)
    waitforWorkers()
    fmt.Println(matches, "matches found")
    fmt.Println(time.Since(start))
}

func waitforWorkers() {
    for {
        select {
        case path := <-searchRequest:
            workerCount++
            go search(path, true)
        case <-workerDone:
            workerCount--
            if workerCount == 0 {
                return
            }
        case <-foundMatch:
            matches++

        }
    }

}
func search(path string, master bool) {
    files, err := ioutil.ReadDir(path)
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, file := range files {
        name := file.Name()
        if name == query {
            foundMatch <- true
            fmt.Println(path + "/" + name)
        }
        if file.IsDir() {
            if workerCount < maxWorkerCount {
                searchRequest <- path + "/" + name
            } else {
                fmt.Println("search in", path+"/"+name)
                // 如果worker数量超过了最大值,就直接递归搜索
                search(path+"/"+name, true)
            }
            // search(path + name + "/")
        }
    }
    if master {
        workerDone <- true
    }
}
分类: go

1 条评论

rtx · 2022-04-20 20:44

站长,您图片不是很清晰,下次建议您用白背景哈

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

ICP备案号: 辽ICP备20003309号