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

基础概念

什么是进程?

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

什么是线程

线程:一条有序的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()
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容