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\n",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)
}
posted @ 2018-02-14 21:07  ninxin18  阅读(533)  评论(0编辑  收藏  举报