goland go程 channel select

go程

补充: 每当有一个进程启动时,系统会自动打开三个文件:

标准输入: stdin

标准输出:stdout

标准错误:stderr

当运行结束,操作系统会自动关闭着三个文件

import (
	"fmt"
	"time"
)

func foo()  {
	fmt.Println("foo ")
	time.Sleep(time.Microsecond * 100)
}

func fun()  {
	fmt.Println("fun ")
	time.Sleep(time.Microsecond * 100)
}

func main() {
	go foo()
	go fun()
	// main 为主go程, 若main运行结束,那么其内部产生的子go程也随即结束
	for {
		;
	}
}

runtime.Gosched()

出让go程运行的cpu权限,当下次再拿到cpu执行权限后,接着Gosched出让的位置继续向后执行代码

import (
	"fmt"
	"runtime"
	"time"
)

func foo()  {
	for   {
		fmt.Println("foo ")
	}	//time.Sleep(time.Microsecond * 100)
}

func main() {
	go foo()
	for {
		runtime.Gosched()  // 出让cpu执行权限
		fmt.Println("main")
	}
}

runtime.Goexit()

立即停止当前的go程,调度器确保所有已注册的defe延迟调用被执行

import (
	"fmt"
	"runtime"
	"time"
)

func test()  {
	defer fmt.Println("bbb")
	//return  // 结束函数
	runtime.Goexit()  // 结束go程,这里结束的是 foo() go程
    defer fmt.Println("ddd")  // 与return一样,此处定义的defer不会被执行
}

func foo()  {
	for   {
		fmt.Println("aaa ")
		test()
		fmt.Println("cccc")
	}	//time.Sleep(time.Microsecond * 100)
}

func main() {
	go foo()
	for {
		;
	}
}
>>>:
aaa
bbb

runtime.GOMAXPROCS()

用来设置可以并行计算的cpu核数最大值,并返回之前的值

func main() {
	n := runtime.GOMAXPROCS(1)
	fmt.Println(n)
    n = runtime.GOMAXPROCS(2)
	fmt.Println(n)
	for {
		go fmt.Print(0)
		fmt.Print(1)
		;
	}
}

更多runtime包详情

channel

管道,并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上进一步降低了变成的难度

特点: channel是一种数据类型,用来解决协程的同步问题,以及协程之间的数据共享问题

goroutine运行在相同的地址空间因此访问共享内存必须做好同步,goroutine奉行通过通信来共享内存,而不是共享内存来通信

channel是引用类型,是个队列,可用于多个goroutine通信,其内部实现的同步,确保并发安全

定义

与map类似,channel也对应一个make创建的地城数据结构的引用,和其他引用类型一样,channel的零值也是nil

ch := make(chan channel中传递的数据类型, channel容量)
容量 = 0: 无缓冲channel  特点:具备同步的能力,常用于同步
容量 > 0: 有缓冲channel  特点:channel内可以存储内容,达到容量上限后才阻塞,常用于异步

注意:
channel有读端和写端,少了任何一端都会死锁报错
同一个go程内只能拥有同一个channel的一端(或读端,或写端)

说明:
同步: 在两个过多个携程(线程)间,保持数据内容一致性的机制

无缓冲channel

利用channel 处理go程对于共享数据的同步问题

func main() {
	ch := make(chan string)
    fmt.Println(len(ch), cap(ch))  // len(ch): channel中剩余内容的数量, cap(ch):channel的容量
	go func() {
		for i:=0;i<5;i++{
			fmt.Println(i)
		}
		ch <- "go程结束"  // 写端
	}()
	str := <-ch
	fmt.Println(str)  // 读端
}

案例:

import (
	"fmt"
	"time"
)

func test(str string)  {
	for _, s := range str{
		//fmt.Println(s)  // 打印的是字节
		fmt.Printf("%c",s)
		time.Sleep(time.Millisecond * 100)
	}
}
// c chan <- int :channel的写端
func foo(str string, c chan <- int)  {
	test(str)
	c <- 1  // 当此处的写端写入内容后,对应的读端会立即读到内容,运行起来
}
// c chan <- int : channel的读端
func fun(str string, c <- chan int)  {
	<- c  // 此时channel的读端是阻塞状态,只有写端写入内容,读端才会继续运行
	test(str)
}

func main() {
	// 主go程中定义,子go程中使用
	// 利用channel的阻塞特点,当channel中没有内容时,就发生阻塞
	ch := make(chan int)    // 无缓冲channel
	go foo("hello", ch)
	go fun("world", ch)

	for {
		;
	}
}

有缓冲channel

当容量慢了之后阻塞,多用于并发

func main() {
	ch := make(chan int, 3)
	fmt.Println(len(ch), cap(ch))  // channel 长度和容量
	go func() {
		for i:=0;i<8;i++{
			ch <- i  // 写端
			fmt.Printf("子go程 i=%d \n", i)
		}

	}()
	for i:=0;i<8;i++{
		num := <-ch
		fmt.Printf("main i=%d \n", num)  // 读端
	}
}

关闭channel

确定不再向对端发送或接收数据时,就可关闭channel

close(channel)  // 大多数用来关闭发送端

注意:

channel不像文件一样需要经常去关闭,只有当你确实没有任何发送的数据了,或者你想显式的结束rang循环之类的,才去关不channel

  • 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);

  • 关闭channel后,可以继续从channel接收数据;

  • 对于nil channel,无论收发都会被阻塞

  • 已经关闭的channel,不能再向其写数据,否则报错

  • 写端已经关闭的channel,可以从中继续读数据,将数据读出后,再继续读,读到0

对端可以判断channel是否关闭
if num, ok := <- ch ; ok == ture{}
如果对端已经关闭, ok == false num无数据
如果对端没关闭, ok == true num是取到的数据
func main() {
	ch := make(chan int, 2)
	fmt.Println(len(ch), cap(ch))  // channel 长度和容量
	go func() {
		for i:=0;i<5;i++{
			ch <- i  // 写端
			fmt.Printf("子go程 i=%d \n", i)
		}
		close(ch)
	}()
	time.Sleep(time.Millisecond * 100)
	for {
		if num, ok := <-ch; ok==true{
			fmt.Printf("main i=%d \n", num)  // 读端
		} else {
			break
		}
	}
}

可以使用 range 来迭代不断操作channel

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        //把 close(c) 注释掉,程序会一直阻塞在 for data := range c 那一行
        close(c)
    }()
    // 类似循环 <- c , range能发现对端是否关闭,对端关闭后,退出循环
    for data := range c {
        fmt.Println(data)
    }
    fmt.Println("Finished")
}

单向channel

默认情况下,通道channel是双向的,也就是,既可以往里面发送数据也可以同里面接收数据。

但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。

// ch1是一个正常的channel,是双向的
var ch1 chan int       ch := make(chan int)
 // ch2是单向channel,只用于写float64数据
var ch2 chan<- float64  ch2 := make(chan <- folat64)
// ch3是单向channel,只用于读int数据
var ch3 <-chan int     ch3 := make(<- chan int)

转换:

双向channel可转换为单向channel, 前提channel内的数据类型要一致

单向channel不能转换为双向channel

    c := make(chan int, 3)
    var send chan<- int = c // send-only
    var recv <-chan int = c // receive-only
    send <- 1
    //<-send //invalid operation: <-send (receive from send-only type chan<- int)
    <-recv
    //recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)

    //不能将单向 channel 转换为普通 channel
    d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
    d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int

channel死锁

  • 单go程死锁: channel至少在两个go程里通信
  • go程间channel访问顺序导致死锁: 读写端要同时有机会运行,并且读端要在写之前运行
  • 多go程交多channel叉死锁:即 互相等待,A go程拿着ch1的时候尝试拿ch2,B go程拿着ch2的时候尝试拿ch1
  •  锁(互斥锁,读写锁)与channel混用, 锁与channel互相等待-----隐性死锁

案例:

var rwMut sync.RWMutex  // 锁只有一把!!!!有读写两种属性

func readGo(in <-chan int, i int)  {
	for{
		rwMut.RLock()  // 加读锁  问题原因: readGo拿到锁, 并等待channel读数据
		num:= <-in
		fmt.Printf("%d th , read %d \n", i, num)
		rwMut.RUnlock()  // 解读锁
	}
}
func writeGo(out chan<- int, i int, qu chan <- int)  {
	for q := 1;q < 500; q++{
		if q == 3{
			qu <- 1
		}
		num := rand.Intn(1000)
		rwMut.Lock()  // 加写锁  问题原因: writeGo等待拿锁后向channel中写入数据 
		out <- num
		fmt.Printf("%d th , write %d \n", i, num)

		time.Sleep(time.Millisecond * 100)
		rwMut.Unlock()  // 解写锁
	}
}

func main() {
	quit := make(chan int)
	ch := make(chan int)

	rand.Seed(time.Now().UnixNano())
	for i:=1;i < 5 ;i++  {
		go readGo(ch, i+1)
	}

	for i:=1;i < 5 ;i++  {
		go writeGo(ch, i+1, quit)
	}
	<- quit
}

select

用来监听channel中的数据流动

select与switch语句相似,select中每个case语句必须是一个IO操作

	select {
	case <- ch1:
		// 如果ch1成功读到数据,则执行case代码块内的代码
	case ch2 <- 5:
		// 如果成功向ch2写数据,则执行case代码块内的代码
	default:
		//如果上面的都没有执行,就进入defer的流程
	}	

注意:

  • 如果给出了 default 语句,那么就会执行 default 语句;select本身不带循环机制

  • 如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去;

func main() {
	ch := make(chan int)
	exit := make(chan bool)
	go func() {
		time.Sleep(time.Millisecond * 100)
		ch <- 1
		exit <- true
	}()
	tag:for   {
		select {
		case <- ch:
			fmt.Println("select ch")
		case <- exit:
			fmt.Println("select exit")
			break tag  // 注意: break 跳出的是select,为了跳出for循环,此处设置标签
			// return  也可以属于return整体跳出
		default:
			time.Sleep(time.Millisecond * 50)
			fmt.Println("defer")
			//如果上面的都没有执行,就进入defer的流程
		}
	}
}
>>>:
defer
defer
select ch
select exit

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值