Channel

goroutine之间的通信使用channel。数据传送是阻塞式的,发了数据之后必须有人来收数据。

func chanDemo() {
	//var c chan int // c == nil
	c := make(chan int)
	go func() {  //这里的匿名函数相当于闭包,引用了外面的c变量
		for {
			n := <-c //开了一个goroutine去接数据
			fmt.Println(n)
		}
	}()
	c <- 1 //往channel中发生数据
	c <- 2
	time.Sleep(time.Millisecond)

}

func main() {
	chanDemo()
}

channel作为参数

func woker(id int, c chan int) {
	for {
		fmt.Printf("Worker %d received %c\n", id, <-c) //因为协程是主动式非抢占,在遇到I/O操作时,会进行调度
	}
}

func chanDemo() {
	var channels [10]chan int
	for i := 0; i < 10; i++ {
		channels[i] = make(chan int)
		go woker(i, channels[i])
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond) //防止main函数先退出

}

func main() {
	chanDemo()
}

channel作为返回值

func createWorker(id int) chan<- int { //返回一个只能往里写数据的channel
	c := make(chan int) //创建一个可以写数据和读数据的channnel
	go func() {
		for {
			fmt.Printf("Worker %d received %c\n", id, <-c)
		}
	}()
	return c
}

func chanDemo() {
	var channels [10]chan<- int //只能往channel中写数据
	for i := 0; i < 10; i++ {
		//channels[i] = make(chan int)
		//go woker(i, channels[i])
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)

}

bufferchannel

func woker(id int, c chan int) {
	for {
		fmt.Printf("Worker %d received %c\n", id, <-c) //因为协程是主动式非抢占,在遇到I/O操作时,会进行调度
	}
}
func bufferedChannel() {
	c := make(chan int, 3) //创建缓冲区为3的channel

	go woker(0, c)
	//只要有人发数据,就必须有人来接数据。缓冲区为3说明 发送的数据小于3时,若没人来接数据,不会出现错误
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Millisecond)
}

channel是可以被发送方进行关闭的,接收方使用两种方法进行判断。

func woker(id int, c chan int) {
	for {
	//当发送方关闭channel之后,接收方还会进行接收,值为channel对应类型的默认值。因此在这里进行判断是否channel关闭
		if n, ok := <-c; ok {
			fmt.Printf("Worker %d received %c\n", id, n) //因为协程是主动式非抢占,在遇到I/O操作时,会进行调度
		} else {
			break
		}
	}
	//第二种方法
	//for n := range c {
	//	fmt.Printf("Worker %d received %c\n", id, n)
	//}
}
func channelClosed() {
	c := make(chan int, 3) //创建缓冲区为3的channel

	go woker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	close(c) 
	time.Sleep(time.Millisecond)
}

不要通过共享内存来通信,通过通信来共享内存。
使用channel等待任务结束。两种方法:使用channel进行传递信息;使用sync.WaitGroup类型的变量。

	data := make(chan int)
	exit := make(chan bool)
	go func() {
		for d := range data {
			fmt.Println(d)
		}
		fmt.Println("recv over.")
		exit <- true
	}()

	data <- 1
	data <- 2
	data <- 3
	close(data)
	fmt.Println("send over.")
	<-exit
func doWoker(id int, c chan int, done chan bool) {
	for n := range c {
		fmt.Printf("Worker %d received %c\n", id, n)
		//done <- true //往done中发送了数据,在外面必须有人来接收数据
		go func() {
			done <- true
		}()
	}
}

type worker struct {
	in   chan int
	done chan bool
}

func createWorker(id int) worker {
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go doWoker(id, w.in, w.done)
	return w
}

func chanDemo() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i)
	}

	for i, worker := range workers {
		worker.in <- 'a' + i
		//<-workers[i].done //只有收到了数据说明打印已经完成了,才会往下执行
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
		//<-workers[i].done
	}

	//将20个数据全部发出去,然后再进行等待
	for _, worker := range workers {
		<-worker.done
		<-worker.done
	}

}

func doWoker(id int, w worker) {
	for n := range w.in {
		fmt.Printf("Worker %d received %c\n", id, n)
		w.done()
	}
}

type worker struct {
	in   chan int
	done func()
}

func createWorker(id int, wg *sync.WaitGroup) worker {
	w := worker{
		in: make(chan int),
		done: func() {
			wg.Done() //完成一次任务调用一次Done()
		},
	}
	go doWoker(id, w)
	return w
}

func chanDemo() {
	var workers [10]worker
	var wq sync.WaitGroup

	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i, &wq)
	}

	wq.Add(20) //添加20个任务
	for i, worker := range workers {
		worker.in <- 'a' + i

	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}
	wq.Wait()
}

使用select进行调度。


func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			time.Sleep(
				time.Duration(rand.Intn(1500)) *
					time.Millisecond)
			out <- i
			i++
		}
	}()
	return out
}
func woker(id int, c chan int) {
	for n := range c {
		time.Sleep(time.Second)
		fmt.Printf("Worker %d received %d\n", id, n)
	}
}
func createWorker(id int) chan<- int {
	c := make(chan int)
	go woker(id, c)
	return c
}

func main() {
	//从c1, c2中读出数据 写入worker中
	var c1, c2 = generator(), generator()
	worker := createWorker(0)

	var values []int //生成和消耗的速度不一样,需要存储接收到的数据

	tm := time.After(10 * time.Second) //返回的是一个channel,10s后往这个channel中发生一个时间
	tick := time.Tick(time.Second)
	for {
		var activeWorker chan<- int // activeWorker 是 nil,在select虽然不会运行错误,但是永远不是被select到
		var activeValue int
		if len(values) > 0 {
			activeWorker = worker
			activeValue = values[0]
		}

		select {
		case n := <-c1:
			values = append(values, n)
		case n := <-c2:
			values = append(values, n)
		case activeWorker <- activeValue:
			values = values[1:]
		case <-time.After(800 * time.Millisecond): //如果两次生成数据的时间大于800ms,则会timeout
			fmt.Println("Time out")
		case <-tick: //每1s查看一下数据的长度
			fmt.Println("queue lens = ", len(values))
		case <-tm:
			fmt.Println("Bye")
			return
		}
	}
}

传统的同步机制:WaitGroup,mutex,conditional variable

type atomicInt struct {
	value int
	m     sync.Mutex
}

func (a *atomicInt) increasement() {
	fmt.Println("safe increasement")
	func() {
		a.m.Lock()
		defer a.m.Unlock()
		a.value++
	}()

}

func (a *atomicInt) get() int {
	a.m.Lock()
	defer a.m.Unlock()
	return a.value
}

select语句的用法


通过select可以监听channel上的数据流动。
每一个case语句里必须是一个I/O操作。

elect {

      case <-chan1:

        // 如果chan1成功读到数据,则进行该case处理语句

      case chan2 <- 1:

        // 如果成功向chan2写入数据,则进行该case处理语句

      default:

        // 如果上面都没有成功,则进入default处理流程

    }

在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。

如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。

如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:

  • 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。

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

go的CSP模型


go语言支持两种形式的并发:多线程共享内存;CSP并发模型。

多线程共享内存模型:在访问数据的时候,通过锁来访问。

CSP并发模型:通过channel和goroutine实现。goroutine是Go中并发的执行单位,channel是各个并发单位之前的通信机制,通俗来说就是各个goroutine之间的管道

传数据用channel <- data,取数据用<-channel,在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

参考一

参考二

你可能感兴趣的:(go)