golang 高级语法 第二篇 并发相关

82 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

三、并发相关

1. 基础

并发:电脑同时听歌,看小说,看电影。cpu根据时间片进行划分,交替执行这个三个程序。我们人可以感觉是同时产生的。

并行:多个CPU(多核)同时执行

c语言里面实现并发过程使用的是多线程(C++的最小资源单元),进程

go语言里面不是线程,而是go程 ==> goroutine,go程是go语言原生支持的

每一个go程占用的系统资源远远小于线程,一个go程大约需要4K~5K的内存资源

一个程序可以启动大量的go程:

  • 线程 ==》几十个
  • go程可以启动成百上千个, ===》 对于实现高并发,性能非常好
  • 只需要在目标函数前加上go关键字即可
package main
​
import (
    "fmt"
    "time"
)
​
//这个将用于子go程使用
func display() {
    count := 1
    for {
        fmt.Println("=============> 这是子go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}
​
func main() {
    //启动子go程
    //go display()
    go func() {
        count := 1
        for {
            fmt.Println("=============> 这是子go程:", count)
            count++
            time.Sleep(1 * time.Second)
        }
    }()
​
    //主go程
    count := 1
    for {
        fmt.Println("这是主go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}
​

1586072019208.png

启动多个字go程,他们会竞争cpu资源

package main
​
import (
    "fmt"
    "time"
)
​
//这个将用于子go程使用
func display(num int) {
    count := 1
    for {
        fmt.Println("=============> 这是子go程:", num, "当前count值:", count)
        count++
        //time.Sleep(1 * time.Second)
    }
}
​
func main() {
    //启动子go程
    for i := 0; i < 3; i++ {
        go display(i)
    }
​
    //go func() {
    //  count := 1
    //  for {
    //      fmt.Println("=============> 这是子go程:", count)
    //      count++
    //      time.Sleep(1 * time.Second)
    //  }
    //}()//主go程
    count := 1
    for {
        fmt.Println("这是主go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}
​

2. 提前退出go程

package main
​
import (
    "fmt"
    "runtime"
    "time"
)
​
//return  ===> 返回当前函数
//exit ===> 退出当前进程
//GOEXIT ===> 提前退出当前go程func main() {
    go func() {
        go func() {
            func() {
                fmt.Println("这是子go程内部的函数!")
                //return //这是返回当前函数
                //os.Exit(-1) //退出进程
                runtime.Goexit() //退出当前go程
            }()
​
            fmt.Println("子go程结束!") //这句会打印吗? 会1:  不打印2
            fmt.Println("go 2222222222 ")
​
        }()
        time.Sleep(2 * time.Second)
        fmt.Println("go 111111111111111")
    }()
​
    fmt.Println("这是主go程!")
    time.Sleep(3 * time.Second)
    fmt.Println("OVER!")
}
​

1586073243844.png

3. 无缓冲管道channel

package main
​
import (
    "fmt"
    "time"
)
​
func main() {
    //sync.RWMutex{}
    //当涉及到多go程时,c语言使用互斥量,上锁来保持资源同步,避免资源竞争问题
    //go语言也支持这种方式,但是go语言更好的解决方案是使用管道、通道 channel
    //使用通道不需要我们去进行加解锁
    //A 往通道里面写数据  B从管道里面读数据,go自动帮我们做好了数据同步//创建管道:  创建一个装数字的管道 ==> channel
    //strChan := make(chan string) //装字符串的管道//make(map[int]string, 10)
    //装数字的管道,使用管道的时候一定要make, 同map一样,否则是nil
    //此时是无缓冲的管道
    //numChan := make(chan int)//有缓冲的管道
    numChan := make(chan int, 10)
​
    //创建两个go程,父亲写数据,儿子读数据
    go func() {
        for i := 0; i < 50; i++ {
            data := <-numChan
            fmt.Println("子go程1 读取数据  ===》 data:", data)
        }
    }()
​
    go func() {
        for i := 0; i < 20; i++ {
            //向管道中写入数据
            numChan <- i
            fmt.Println("子go程2 写入数据:", i)
            //time.Sleep(1 * time.Second)
        }
    }()
​
    for i := 20; i < 50; i++ {
        //向管道中写入数据
        numChan <- i
        fmt.Println("======> 这是主go程, 写入数据:", i)
        //time.Sleep(1 * time.Second)
    }
​
    time.Sleep(5 * time.Second)
}
​

1586074416793.png

4. 有缓冲区管道

package main
​
import (
    "fmt"
    "time"
)
​
func main() {
    //numsChan := make(chan int, 10)
    //1. 当缓冲写满的时候,写阻塞,当被读取后,再恢复写入
    //2. 当缓冲区读取完毕,读阻塞
    //3. 如果管道没有使用make分配空间,那么管道默认是nil的,读取、写入都会阻塞
    //4. 对于一个管道,读与写的次数,必须对等var names chan string //默认是nil的
    names = make(chan string, 10)
​
    go func() {
        fmt.Println("names:", <-names)
    }()
​
    names <- "hello" //由于names是nil的,写操作会阻塞在这里
    time.Sleep(1 * time.Second)
​
    numsChan1 := make(chan int, 10)
​
    //写
    go func() {
        for i := 0; i < 50; i++ {
            numsChan1 <- i
            fmt.Println("写入数据:", i)
        }
    }()
​
    //读,当主程序被管道阻塞时,那么程序将锁死崩溃
    //要求我们一定要读写次数保持一致
    func() {
        for i := 0; i < 60; i++ {
            fmt.Println("主程序准备读取数据.....")
            data := <-numsChan1
            fmt.Println("读取数据:", data)
        }
    }()
​
    for {
        ;
    }
}
​
  1. 当管道的读写次数不一致的时候

    1. 如果阻塞在主go程,那么程序会崩溃
    2. 如果阻塞在子go程,那么会出现内存泄露

5. for range遍历

package main
​
import "fmt"func main() {
​
    numsChan2 := make(chan int, 10)
​
    //写
    go func() {
        for i := 0; i < 50; i++ {
            numsChan2 <- i
            fmt.Println("写入数据:", i)
        }
        fmt.Println("数据全部写完毕,准备关闭管道!")
        close(numsChan2)
    }()
​
    //遍历管道时,只返回一个值
    //for range是不知道管道是否已经写完了,所以会一直在这里等待
    //在写入端,将管道关闭,for range遍历关闭的管道时,会退出
    for v := range numsChan2 {
        fmt.Println("读取数据 :", v)
    }
​
    fmt.Println("OVER!")
}