zoukankan      html  css  js  c++  java
  • Go开发[八]goroutine和channel

    进程和线程

    进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。

    线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

    一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行.

    并发和并行

    多线程程序在一个核的cpu上运行,就是并发

    多线程程序在多个核的cpu上运行,就是并行

    协程和线程

    协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的

    线程:一个线程上可以跑多个协程,协程是轻量级的线程。

    goroutine调度模型

    简单的例子

    package main
    
    import "fmt"
    import "time"
    
    func test() {
       var i int
       for {
          fmt.Println(i)
          time.Sleep(time.Second*5)
          i++
       }
    }
    
    func main() {
       go test()
       for {
          fmt.Println("i' running in main")
          time.Sleep(time.Second)
       }
    }
    

    设置golang运行的cpu核数

    go1.8版本以上默认设置了

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	num := runtime.NumCPU()
    	runtime.GOMAXPROCS(num)
    	fmt.Println(num)
    }
    

    不同goroutine之间通讯

    全局变量和锁同步

    package main
    
    import (
       "fmt"
       "time"
       "sync"
    )
    
    var (
       m = make(map[int]uint64)
       lock sync.Mutex
    )
    
    type task struct {
       n int
    }
    
    func calc(t *task) {
       var sum uint64
       sum = 1
       for i:=1;i<t.n;i++{
          sum *= uint64(i)
       }
       fmt.Println(t.n,sum)
       lock.Lock()
       m[t.n] = sum
       lock.Unlock()
    }
    
    func main()  {
       for i :=0;i<20;i++{
          t := &task{n:i}
          go calc(t)
       }
       time.Sleep(10 * time.Second)
       /*
       lock.Lock()
       for k,v := range m{
          fmt.Printf("%d!=%v
    ",k,v)
       }
       lock.Unlock()*/
    }
    

    Channel

    channel

    a. 类似unix中管道(pipe)

    b. 先进先出

    c. 线程安全,多个goroutine同时访问,不需要加锁

    d. 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

    channel初始化

    使用make进行初始化,比如:

    var test chan int

    test = make(chan int, 10)

    var test chan string

    test = make(chan string, 10)

    从channel读取数据

    var testChan chan int
    testChan = make(chan int, 10)
    var a int
    a = <- testChan
    

    向channel写入数据

    var testChan chan int
    testChan = make(chan int, 10)
    var a int  = 10
    testChan <- a
    

    goroutine和channel相结合

    package main
    
    import (
       "fmt"
       "time"
    )
    
    func write(ch chan int) {
       for i := 0; i < 100; i++ {
          ch <- i
          fmt.Println("put data:", i)
       }
    }
    
    func read(ch chan int) {
       for {
          var b int
          b = <-ch
          fmt.Println(b)
          time.Sleep(time.Second)
       }
    }
    
    func main() {
       intChan := make(chan int, 10) //testChan是带缓冲区的chan,一次可以放10个元素
       go write(intChan)
       go read(intChan)//不读的话会引发阻塞
       time.Sleep(10 * time.Second)
    }
    

    chan之间的同步

    package main
    
    import "fmt"
    
    func send(ch chan int, exitChan chan struct{}) {
    	for i := 0; i < 10; i++ {
    		ch <- i
    	}
    	close(ch)
    	var a struct{}
    	exitChan <- a
    }
    
    func recv(ch chan int, exitChan chan struct{}) {
    	for {
    		v, ok := <-ch
    		if !ok {
    			break
    		}
    		fmt.Println(v)
    	}
    	var a struct{}
    	exitChan <- a
    }
    
    func main() {
    	var ch chan int
    	ch = make(chan int, 10)
    	exitChan := make(chan struct{}, 2)
    
    	go send(ch, exitChan)
    	go recv(ch, exitChan)
    
    	var total = 0
    	for _ = range exitChan {
    		total++
    		if total == 2 {
    			break
    		}
    	}
    }
    

    例子:

    package main
    
    import (
       "fmt"
    )
    //计算1000以内的素数
    func calc(taskChan chan int, resChan chan int, exitChan chan bool) {
       for v := range taskChan {
          flag := true
          for i := 2; i < v; i++ {
             if v%i == 0 {
                flag = false
                break
             }
          }
          if flag {
             resChan <- v
          }
       }
       fmt.Println("exit")
       exitChan <- true
    }
    
    func main() {
       intChan := make(chan int, 100)
       resultChan := make(chan int, 100)
       exitChan := make(chan bool, 8)
    
       go func() {
          for i := 0; i < 1000; i++ {
             intChan <- i
          }
          close(intChan)
       }()
    
       for i := 0; i < 8; i++ {
          go calc(intChan, resultChan, exitChan)
       }
    
       //等待所有计算的goroutine全部退出
       go func() {
          for i := 0; i < 8; i++ {
             <-exitChan
             fmt.Println("wait goroute ", i, " exited")
          }
          close(resultChan)
       }()
    
       for v := range resultChan {
          //fmt.Println(v)
          _ = v
       }
    }
    

    chan的关闭

    1.使用内置函数close进行关闭,chan关闭之后,for range遍历chan中已经存在的元素后结束

    2.使用内置函数close进行关闭,chan关闭之后,没有使用for range的写法需要使用,v, ok := <- ch进行判断chan是否关闭

    package main
    
    import "fmt"
    
    func main() {
       var ch chan int
       ch = make(chan int, 10)
       for i := 0; i < 10; i++ {
          ch <- i
       }
    
       close(ch)
       for {
          var b int
          b,ok := <-ch
          if ok == false {
             fmt.Println("chan is close")
             break
          }
          fmt.Println(b) //谨防死循环
       }
    }
    

    for range遍历chan

    package main
    
    import "fmt"
    
    func main() {
       var ch chan int
       ch = make(chan int, 1000)
       for i := 0; i < 1000; i++ {
          ch <- i
       }
    
       close(ch)
       for v := range ch {
          fmt.Println(v)
       }
    }
    

    chan的只读和只写

    a. 只读chan的声明

    Var 变量的名字 <-chan int
    Var readChan <- chan int
    

    b. 只写chan的声明

    Var 变量的名字 chan<- int
    Var writeChan chan<- int
    

    对chan进行select操作

    select {
         case u := <- ch1:
         case e := <- ch2:
         default:   
      }
    

    下面是死锁了,阻塞了

    package main
    
    import "fmt"
    
    func main()  {
       var ch chan int
       ch = make(chan int,10)
       for i :=0;i<10;i++{
          ch <- i
       }
       for {
          var b int
          b = <-ch
          fmt.Println(b)
       }
    }
    

    select解决阻塞

    //@Time  : 2018/2/1 22:14
    //@Author: ningxin
    package main
    
    import (
       "fmt"
       "time"
    )
    
    func main()  {
       var ch chan int
       ch = make(chan int,10)
       for i :=0;i<10;i++{
          ch <- i
       }
       for {
          select{
          case v:= <-ch:
             fmt.Println(v)
          default:
             fmt.Println("get data timeout")
             time.Sleep(time.Second)
          }
       }
    }
    

    定时器的使用

    package main
    
    import (
    	"fmt"
    	"time"
    )
    func queryDb(ch chan int) {
    
    	time.Sleep(time.Second)
    	ch <- 100
    }
    func main() {
    	ch := make(chan int)
    	go queryDb(ch)
    	t := time.NewTicker(time.Second)
    
    	select {
    	case v := <-ch:
    		fmt.Println("result", v)
    	case <-t.C:
    		fmt.Println("timeout")
    	}
    }
    

    超时控制

    package main
    
    import (
       "fmt"
       "runtime"
       "time"
    )
    
    func main() {
       num := runtime.NumCPU()
       runtime.GOMAXPROCS(num - 1)
       for i := 0; i < 16; i++ {
          go func() {
             for {
                t := time.NewTicker(time.Second)
                select {
                case <-t.C:
                   fmt.Println("timeout")
                }
                t.Stop()
             }
          }()
       }
    
       time.Sleep(time.Second * 100)
    }
    

    goroutine中使用recover

    应用场景,如果某个goroutine panic了,而且这个goroutine里面没有捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产生一个goroutine,就需要写下recover

    package main
    
    import (
       "fmt"
       "runtime"
       "time"
    )
    
    func test() {
       defer func() {
          if err := recover(); err != nil {
             fmt.Println("panic:", err)
          }
       }()
       var m map[string]int
       m["stu"] = 100
    }
    
    func calc() {
       for {
          fmt.Println("i'm calc")
          time.Sleep(time.Second)
       }
    }
    
    func main() {
       num := runtime.NumCPU()
       runtime.GOMAXPROCS(num - 1)
       go test()
       for i := 0; i < 2; i++ {
          go calc()
       }
       time.Sleep(time.Second * 10000)
    }
    
  • 相关阅读:
    一些精简的小技巧
    POJ题目分类(转)
    【慢慢学算法】:连通图
    【菜鸟做水题】: 杭电1004
    杭电ACM试题分类,一步一个脚印!(转)
    【慢慢学Android】:获得当前时间
    【慢慢学算法】:小白鼠排队
    【慢慢学Android】:12.Notification示例
    【慢慢学Android】:13.打电话代码
    “/”应用程序中的服务器错误。
  • 原文地址:https://www.cnblogs.com/ningxin18/p/8448903.html
Copyright © 2011-2022 走看看