golang(7):channel读写

goroutine 

1、进程和线程

A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
C. 一个进程可以创建和撤消多个线程;同一个进程中的多个线程之间可以并发执行

2. 协程和线程

协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户线线程的调度也是自己实现的 (go func() 起的就是协程)
线程:一个线程上可以跑多个协程,协程是轻量级的线程 (一个物理线程可以跑多个协程)

3. 不同 goroutine 之间进行通信

// a. 全局变量 和 锁同步
// b. Channel

打印1到10所有数的阶乘(全局变量 和 锁同步)

// 示例代码:
package main 

import (
    "fmt"
    "time"
    "sync"        // 修改公共数据要加锁
)

var (
    m = make(map[int]uint64)        // 定义一个全局变量的切片,用于保存生成的阶乘结果
    lock sync.Mutex                        // 定义一个互斥锁
)

type task struct {
    n int
}

func calc(p *task){
    var sum uint64
    sum = 1

    for i := 1; i <= p.n; i++ {
        sum *= uint64(i)        // i 是 int 类型, sum 是 uint 类型,要强转一下
    }

    lock.Lock()            // 加锁;修改公共数据    要加锁
    m[p.n] = sum
    lock.Unlock()        // 释放锁

}

func main(){
    for i:= 0; i <= 10; i++ {
        t := &task{i}
        fmt.Printf("%d addr:%p\n",i,&t)
        go calc(t)        // 开启 11 个新线程
    }    

    time.Sleep(time.Second * 5)

    lock.Lock()        // 由于读取的公共数据,而且不知道 sleep 5 秒后所有的 goroutine 是否已经全部执行完,所以此时读取 m 也要加锁
    for k,v := range m {
        fmt.Printf("%d! = %v\n",k,v)
    }
    lock.Unlock()


    // 如果想知道你的程序在执行过程中有没有资源竞争的情况,可以在 build 的时候加上 --race 参数
}


// 运行结果:
[root@NEO example01_goroutine01_factorial]# go run main/main.go
0 addr:0xc00000e030            // 每次生成的 t 都是不同的地址
1 addr:0xc00000e040
2 addr:0xc00000e048
3 addr:0xc00000e050
4 addr:0xc00000e058
5 addr:0xc00000e060
6 addr:0xc00000e068
7 addr:0xc00000e070
8 addr:0xc00000e078
9 addr:0xc00000e080
10 addr:0xc00000e088
10! = 3628800
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
0! = 1
1! = 1
2! = 2
8! = 40320
9! = 362880
[root@NEO example01_goroutine01_factorial]# 

// 第一次写上面的代码的时候,犯了如下错误:
func main(){
    var t task        // 错误原因: 全程只生成了这一个 t 
    for i:= 0; i <= 10; i++ {
        t.n = i        // 每次修改 t.n 是都是在对同一个 t 的 n 作修改
        fmt.Printf("%d addr:%p\n",i,&t)
        go calc(&t)        //  calc(&t) 在调用 t.n 时,好多线程用的都是同一个 t 中的 n
    }    
    
    ...
}

channel

channel概念:

a. 类似 unix 中的管道(pipe)
b. 先进先出
c. 线程安全,多个 goroutine 同时访问,不需要加锁
d. channel 是有类型的,一个整数的 channel 只能存放整数

channel 声明

// var 变量名 chan 类型
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu

示例代码:

package main

import "fmt"

type student struct{
    name string
}

func main(){
    var mapChan chan map[string]string                // 声明一个 channel;chan + 变量类型 才是管道的类型
    mapChan = make(chan map[string]string,10)        // 管道初始化;管道满了后就不能再往里面写了(也可以阻塞)
    m := make(map[string]string,16)                    // 声明并初始化;超过16个元素后 m 会自动扩容
    m["stu01"] = "neo01"
    m["stu02"] = "neo02"
    mapChan <- m            // 把 m 写入 mapChan 管道

    var stuChan chan *student
    stuChan = make(chan *student,10)    // 初始化的时候也要 chan *student
    stu := student{name:"neo"}
    stuChan <- &stu        // 写入是传入地址

    var interfChan chan interface{}            // interface{} --> 这样 channel 中可存任何类型
    interfChan = make(chan interface{},10)
    interfChan <- stu

    /*
    stu01 :=<- interfChan            // 读取 interfChan;声明并初始化 stu01
    fmt.Printf("stu01:%v\n",stu01)
    */
    var stu01 interface{}        // stu01 是一个接口类型
    stu01 =<- interfChan

    stu02,ok := stu01.(student)            // 断言
    if !ok {
        fmt.Println("can not convert")
        return
    }
    fmt.Println("stu02:",stu02)
    
}


// 运行结果:
[root@NEO example02_channel01]# go run main/main.go
stu02: {neo}
[root@NEO example02_channel01]#

channel 和 goroutine 相结合的事例(一个goroutine写,一个goroutine读)

// 示例代码:
package main

import (
    "fmt"
    "time"
)

func write(ch chan int){    // 管道 chan 后面一定要加上变量类型
    for i := 0; i < 15; i++{
        ch <- i
        fmt.Printf("put data i:%d\n",i)
    }    
}

func read(ch chan int){
    for {
        var b int
        b =<- ch
        fmt.Println(b)
        time.Sleep(time.Second)
    }
}


func main() {
    var intChan chan int
    intChan = make(chan int,10)        // 管道超过10个元素后,就不能再管道中添加元素(再加就也会阻塞)

    go write(intChan)
    go read(intChan)

    time.Sleep(time.Second * 20)
}


// 运行结果:
[root@NEO example03_chan_goroute]# go run main/main.go
put data i:0
put data i:1
put data i:2
put data i:3
put data i:4
put data i:5
put data i:6
put data i:7
put data i:8
put data i:9
0
put data i:10
1
put data i:11
2
put data i:12
3
put data i:13
4
put data i:14
5
6
7
8
9
10
11
12
13
14
[root@NEO example03_chan_goroute]# 
// 上面代码的运行现象:同时开了 read() 和 write() 两个 goroutine,write() 中的前10个元素会立马写入到 管道 中,然后 write() 会阻塞,此时 read() 每秒读取一个,write() 再写入一个

猜你喜欢

转载自www.cnblogs.com/neozheng/p/11318803.html