美文网首页
区块链 GO 并发编程 之 主线程与子线程

区块链 GO 并发编程 之 主线程与子线程

作者: ze__lin | 来源:发表于2018-01-29 22:33 被阅读0次

    基础概念

    什么是进程?

    进程:一个正在运行的程序一般是一个进程,一个进程可以包含多个线程
    每个进程都有自己的独立的地址空间(内存空间),简单的理解一个运行程序为一个进程

    什么是线程

    线程:一条有序的CPU命令的集合体
    线程有就绪,阻塞和运行三个基本状态

    什么是多线程

    多线程:多条有序的CPU命令的结合体

    备注:一个CPU在同一时刻只能执行一个CPU命令

    假设只有一个CPU,能不能进行多线程编程

    3个线程: 并发编程
    线程1: 5个命令
    线程2:3个命令
    线程3: 8个命令

    3个线程: 串行编程
    线程1: 5个命令
    线程2:3个命令
    线程3: 8个命令

    线程1,线程2,线程3,假设在只有一个CPU的情况,编发编程,

    需要通过上下文切换,实现上下文的切片,时间片的轮转分配
    1.并发编程:

    多个线程,会有时间片的分配问题,多个线程之间会不断的切换

    2.串行编程:

    根据添加线程的顺序,按照顺序一一执行

    多线程编程优点:分线程可以处理耗时操作,不会出现主线程阻塞
    多线程编程缺点:资源竞争,内存消耗,死锁

    有如下两种
    1.物理CPU:
    2.逻辑CPU

    一个物理CPU可以虚拟出多个逻辑CPU
    8核同一时刻可以同时最多执行8个CPU命令

    Go 运用线程编写程序

    golang的线程是一种并发机制

    定义好函数,要实现这个函数的并发执行,只要用go关键字就可以了

    package main
    
    import "fmt"
    
    // runtime:有两种形式,一种是一条直线,另一种是一个圈
    // 当前demo中,仍然是一条直线
    // 当程序执行到主函数中最后一个}时,整个程序结束
    //下面程序创建了两个分线程,加上主线程,一共三个线程
    func main() {
        fmt.Println("foo() start")
        go foo() //在方法名前面+关键字go 就开        启了一个分线程   把foo()的执行放到分线程里面
        fmt.Println("foo() end")
        fmt.Println("bar() start")
        go bar() //把bar()的执行放到分线程里面
        fmt.Println("bar() end")
        f1()//此方法在主线程
    }
    
    func f1() {
        for k := 0; k < 10; k++ {
            fmt.Println("k:", k)
        }
    }
    
    func foo() {
        for i := 0; i < 100; i++ {
            fmt.Println("i:", i)
        }
    }
    
    func bar() {
        for j := 0; j < 200; j++ {
            fmt.Println("j:", j)
        }
    }
    
    输出为:
    foo() start
    foo() end
    bar() start
    bar() end
    k: 0
    k: 1
    k: 2
    k: 3
    k: 4
    k: 5
    k: 6
    k: 7
    k: 8
    k: 9
    

    这里,我们就可以看到子线程的死亡有两种途径,一种是子线程运行结束,另一种是主线程运行结束runtime主动杀死子线程。

    下面runtime是跑一个圈:

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    /*
    const (
        Nanosecond  Duration = 1
        Microsecond          = 1000 * Nanosecond
        Millisecond          = 1000 * Microsecond
        Second               = 1000 * Millisecond
        Minute               = 60 * Second
        Hour                 = 60 * Minute
    )
    */
    
    var wg sync.WaitGroup//创建一个wg 对象,类型为sync.WaitGroup(sync里面的一个WaitGroup类)
    
    
    func main() {
        wg.Add(2) ////添加线程的个数并统计管理所添加的线程
        go foo()  //线程1
        go bar()  //线程2
        wg.Wait() //让主线程等待分线程执行,只有队列中counter计数器的值变为0时,主线程才会运行wg.Wait()这条命令后面的命令直至结束,从而让runtime编程跑了一圈
    
    }
    
    func foo() {
        for i := 0; i < 45; i++ {
            fmt.Println("Foo:", i)
            time.Sleep(3 * time.Second) //耗时3秒
        }
        wg.Done()//这行命令是添加在分线程所要运行的地方里的,当分线程中的任务处理完成后,counter计数器减1
    }
    
    func bar() {
        for i := 0; i < 45; i++ {
            fmt.Println("Bar:", i)
            time.Sleep(20 * time.Second) //耗时20秒
        }
        wg.Done()
    }
    
    

    设置机器能够参与执行的CPU的个数,
    速度会提升很多

    package main
    
    import (
        "fmt"
        "runtime"
        "sync"
        "time"
    )
    
    var wg sync.WaitGroup
    
    // runtime.NumCPU() 逻辑CPU个数
    // runtime.GOMAXPROCS设置机器能够参与执行的CPU的个数
    // init()方法会在main函数之前执行
    func init() {
        fmt.Println("init()")
        fmt.Println(runtime.NumCPU())
    
        runtime.GOMAXPROCS(runtime.NumCPU())
    }
    
    func main() {
        fmt.Println("main() start()")
        wg.Add(2)
        go foo()
        go bar()
        wg.Wait()
    }
    
    func foo() {
        for i := 0; i < 45; i++ {
            fmt.Println("Foo:", i)
            time.Sleep(3 * time.Millisecond)
        }
        wg.Done()
    }
    
    func bar() {
        for i := 0; i < 45; i++ {
            fmt.Println("Bar:", i)
            time.Sleep(20 * time.Millisecond)
        }
        wg.Done()
    }
    
    

    下面这个程序
    主线程会等待子线程执行完,整个程序才结束

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    // go run -race main.go  检查是否有资源竞争
    // vs
    // go run main.go
    var wg sync.WaitGroup
    
    func main() {
        wg.Add(1)
        a := 1
        go func() { //子线程
            a = 2
            fmt.Println("a is func ", a)
            wg.Done()
        }()
        a = 3
        fmt.Println("a is main", a)
        wg.Wait()
    }
    输出为
    a is main 3
    a is func  2
    

    下面这个demo,两个子线程执行完,程序才结束,
    但哪个线程先执行不固定

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var ticktCount int = 100
    
    var wg sync.WaitGroup //一个队列
    
    func main() {
        wg.Add(2)
        go f1()
        go f2()
        wg.Wait()
    }
    
    func f1() {
        ticktCount--
        fmt.Println("f1", ticktCount)
        wg.Done()
    }
    
    func f2() {
        ticktCount = ticktCount - 2
        fmt.Println("f2", ticktCount)
        wg.Done()
    }
    输出为:
    f2 98
    f1 97
    

    若代码有资源竞争,对数据加锁
    对子线程进行管理

    package main
    
    import (
        "fmt"
        "math/rand"
        "sync"
        "time"
    )
    
    // 当前代码有资源竞争,我们需要对数据加锁
    
    // go run -race main.go  竞争检测
    // vs
    // go run main.go
    
    var wg sync.WaitGroup //管理线程的队列
    var counter int       //全局变量
    var mutex sync.Mutex  // 互斥
    
    func main() {
        wg.Add(2)              //wg中的counter为2
        go incrementor("Foo:") //新增一个线程
        go incrementor("Bar:") //新增一个线程
        wg.Wait()              //主线程需要等待子线程的任务执行完成,才会继续往下执行
        fmt.Println("Final Counter:", counter)
    }
    
    func incrementor(s string) {
        // 不设置时间种子的话,每次生成的rand值相同
        rand.Seed(time.Now().UnixNano())
        for i := 0; i < 20; i++ {
            // rand.Intn 生成随机数
            time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
            mutex.Lock() //上锁,上锁后,被锁定的内容不会被两个或者多个线程同时竞争
            counter++
            fmt.Println(s, i, "Counter:", counter)
            mutex.Unlock() //解锁
        }
        wg.Done()
    }
    
    

    除上面方法外
    还可以使用原子操作

    package main
    
    import (
        "fmt"
        "math/rand"
        "sync"
        "sync/atomic" //原子操作
        "time"
    )
    
    // 当前代码有资源竞争,我们需要对数据加锁
    
    // go run -race main.go  竞争检测
    // vs
    // go run main.go
    
    var wg sync.WaitGroup //管理线程的队列
    var counter int64       //全局变量
    
    // counter
    // 原子操作:同一时刻同一条数据只能被一个线程拥有
    // 非原子操作:同一时刻,同一条数据可能同时会被多个线程竞争
    
    func main() {
        wg.Add(2)              //wg中的counter为2
        go incrementor("Foo:") //新增一个线程
        go incrementor("Bar:") //新增一个线程
        wg.Wait()              //主线程需要等待子线程的任务执行完成,才会继续往下执行
        fmt.Println("Final Counter:", counter)
    }
    
    func incrementor(s string) {
        // 不设置时间种子的话,每次生成的rand值相同
        rand.Seed(time.Now().UnixNano())
        for i := 0; i < 20; i++ {
            // rand.Intn 生成随机数
            time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
    
            //原子操作,下面的代码是让counter+1
            atomic.AddInt64(&counter, 1)
            fmt.Println(s, i, "Counter:", counter)
    
        }
        wg.Done()
    }
    
    

    相关文章

      网友评论

          本文标题:区块链 GO 并发编程 之 主线程与子线程

          本文链接:https://www.haomeiwen.com/subject/kiofzxtx.html