Go_并发编程

gorouting

goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

package main

import (
	"fmt"
)

func main() {
	go Add(1, 1)
}

func Add(x, y int) {
z := x + y
fmt.Println(z)
}

  在一个函数调用前加上 go 关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃

通信

传统方式: 共享数据加锁

package main
import "fmt"
import "sync"
import "runtime"
var counter int = 0
func Count(lock *sync.Mutex) {
	lock.Lock()
	counter++
	fmt.Println(counter)
	lock.Unlock()
}
func main() {
	lock := &sync.Mutex{}
	for i := 0; i < 10; i++ {
		go Count(lock)
	}
	for {
		lock.Lock()
		c := counter
		lock.Unlock()
		runtime.Gosched()
		if c >= 10 {
			break
		}
	}
}

  Go语言提供的是另一种通信模型,即以消息机制而非共享内存作为通信方式.

消息机制认为每个并发单元是自包含的、独立的个体,并且都有自己的变量,但在不同并发单元间这些变量不共享。每个并发单元的输入和输出只有一种,那就是消息。这有点类似于进程的概念,每个进程不会被其他进程打扰,它只做好自己的工作就可以了。不同进程间靠消息来通信,它们不会共享内存。

  channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。

channel

特点

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

声明

// var 变量名 chan 类型
// 比如下面的数字类型的示例
func main() {
	var intChan chan int
	intChan = make(chan int, 10)
	intChan <- 10
}

操作

// 初始化
intChan = make(chan int, 10)
// 存值
intChan <- 10
// 取值 
a := <- intChan

示例

package main

import "fmt"

type student struct {
	name string
}


func main() {
	var stuChan chan interface{}
	stuChan = make(chan interface{}, 10)
	stu := student{name:"stu1"}
	stuChan <- &stu

	var stu1 interface{}
	stu1 = <- stuChan
	var stu2 *student
	stu2, ok := stu1.(*student) // 接口转类型
	if !ok{
		fmt.Println("can not convert")
		return
	}
	fmt.Println("stu2",stu2)
}

阻塞: 取不到元素会等待, 放不进去也会等待

package main
import(
	"time"
	"fmt"
)
func main(){
	var ch chan int
	ch = make(chan int, 10)
	func (ch chan int){
		time.Sleep(2*time.Second)
		ch <- 10
	}(ch)
	a := <- ch
	fmt.Println(a)
}

关闭一个channel, 使其只能进行读操作,且读完后不会阻塞

// 关闭一个channel(只能读,不能写), 以及判断是否关闭
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)
	}
}

循环一个channel

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

通过channel阻塞的特性, 我们可以通过channel来等待gorouting结束

package main

import (
	"fmt"
)

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 // 计算结束后将一个结果放入已完成的channel中
}

func main() {
	intChan := make(chan int, 1000)
	resChan := make(chan int, 1000)
	exitChan := make(chan bool, 8)
	go func(){
		for i:= 0;i<10000 ;i++{
			intChan <- i
		}
		close(intChan)
	}() // 将数字全部放入一个channel


	for i:= 0; i<8; i++{
		go calc(intChan, resChan, exitChan)
	} // 开启8个gorouting去计算

	// 等待所有计算的gorouting全部退出
	go func(){
		for i:= 0; i<8; i++{
			<- exitChan // 阻塞等待完成
		}
		close(resChan)
	}()

	for v:= range resChan{
		fmt.Println(v)
	}
}

select语法

select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由case 语句来描述。与 switch 语句可以选择任何可使用相等比较的条件相比, select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个IO操作,大致的结构如下:

select {
    case <-chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
}

  可以看出, select 不像 switch ,后面并不带判断条件,而是直接去查看 case 语句。每个case 语句都必须是一个面向channel的操作。

基于此功能可以实现跳过阻塞

func main(){
    car 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)
        }
    }
}

  如果channel去不到值就会执行默认操作, 也可以接多个IO操作

超时机制

Go语言没有提供直接的超时处理机制,但我们可以利用 select 机制。虽然 select 机制不是专为超时而设计的,却能很方便地解决超时问题。因为 select 的特点是只要其中一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。

func main(){
    car ch chan int
    ch = make(chan int, 10)
    
    for i:=0; i<10; i++{
        ch <-i
    }
    
    for{
        // 首先,我们实现并执行一个匿名的超时等待函数
        timeout := make(chan bool, 1)
        go func() {
            time.Sleep(1e9) // 等待1秒钟
            timeout <- true
        }()
        // 然后我们把timeout这个channel利用起来
        select{
            case v:= <-ch
                fmt.Println(v)
            case <-timeout
                // 一直没有从ch中读取到数据,但从timeout中读取到了数据
            default:
                fmt.Println("get data timeout")
                time.Sleep(time.Second)
        }
    }
}    

单向channel

顾名思义,单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。

我们在将一个channel变量传递到一个函数时,可以通过将其指定为单向channel变量,从而限制该函数中可以对此channel的操作,比如只能往这个channel写,或者只能从这个channel读。

单向channel变量的声明非常简单,如下:

var ch1 chan int  // ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int  // ch3是单向channel,只用于读取int数据

限制操作

func Parse(ch <-chan int) {
    for value := range ch {
        fmt.Println("Parsing value", value)
    }
}

同步锁

互斥锁 : 只有一个线程能够执行加锁代码

var lock sync.Mutex
func main(){

    for i := 0; i < 2; i++ {
        go func(b map[int]int) {
            lock.Lock()
            b[8] = rand.Intn(100)
            lock.Unlock()
        }(a)
    }
}

读写锁 : 分读锁与写锁, 一个线程获得读锁时其他线程依旧可以进行读操作.而一个线程获得写锁时, 其他线程不能进行操作

func main(){
        var rwLock sync.RWMutex
        rwLock.Lock() // 写suo
        b[8] = rand.Intn(100)
        time.Sleep(10 * time.Millisecond)
        rwLock.Unlock()
        
        rwLock.RLock() // 读锁
        time.Sleep(time.Millisecond)
        fmt.Println(a)
        rwLock.RUnlock()
}

单次锁 : 只有此一次会被执行

var a string
var once sync.Once
func setup() {
    a = "hello, world"
}
func doprint() {
    once.Do(setup)
    print(a)
}
func twoprint() {
    go doprint()
    go doprint()
}

只能执行没有参数没有返回值的函数

package sync

import (
    "sync/atomic"
)

type Once struct {
	m    Mutex
	done uint32
}


func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	// Slow-path.
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

  

  

posted @ 2018-09-11 10:45  瓜田月夜  阅读(122)  评论(0编辑  收藏  举报