Go语言并发编程基础

0x01 goroutine

go是在语言层面上支持并发编程,使用方法非常简单,通过go关键字后面加入函数即可。

func main() {
	for i := 0; i < 1000; i++ {
		go func(i int) {
			fmt.Printf("hello goroutine %d\n", i)
		}(i)
	}
	time.Sleep(time.Microsecond)
}

上面的代码就是开了1000协程协程是一种轻量级的线程,非抢占式多任务处理,由协程主动交出控制权,是编译器、解释器、虚拟机层面的多任务,多个协程可能在一个或多个线程上运行。

那么怎么交出控制权呢?

  • IOselect
  • runtime.Gosched()
  • channel
  • 等待锁
  • 函数调用

0x02 channel

channel的概念有点类似于进程中的pipe,不过channel是用于goruntine之间的交互。在go语言中channel的定义非常简单,我们通过chan这个关键字定义

c := make(chan int)

我们可以通过channel收发数据

c <- 1 // 发送
n := <-c //接收

也就是channel在左边表示发送数据,而channel在右边表示接收数据。例子

func chanDemo() {
	c := make(chan int)
	go func() {
		for {
			n := <-c
			fmt.Println(n)
		}
	}()
	c <- 1
}

我们也可以创建只发送或者只接收数据的channel

c := make(chan<- int) // 只发送
c := make(<-chan int) // 只接收

我们可以在创建channel的时候设置缓冲区大小

c := make(chan int, 3) // buf size 3

channel的关闭(表示我们发送方发完了)

close(c)

判断channel关闭的几种方式

  • n, ok := <-c
    if !ok {
        break
    }
    
  • for n := range c { //也就是将管道当做一个容器使用
    }
    

当我们工作协程结束后需要通知我们的主协程,可以这样做

w := worker{
    in: make(chan int),
    done: make(chan bool),
}

建立一个结构体,包含我们需要返回的确认结果,我们也通过goroutine去传出我们任务结束这个消息。

当多个协程结束的时候通知主协程,我们可以单独开一个协程通知,因为goroutine在发送的过程中没有接收的话就会阻塞(类似于pipe)。

func doWorker(id int, c chan int, done chan bool) {
	for {
		fmt.Printf("Worker %d reveived %c\n", id, <-c)
		go func() {
			done <- true
		}()
	}
}

上面是一种简单的处理思路。还有一种处理思路是使用sync.WaitGroup

func doWorker(id int, c chan int, wg *sync.WaitGroup) {
	for {
		fmt.Printf("Worker %d reveived %c\n", id, <-c)
        wg.Done()
	}
}

type worker struct {
	in chan int
	wg *sync.WaitGroup
}

func createWorker(id int, wg *sync.WaitGroup) worker {
	w := worker{
		in: make(chan int),
		wg: wg,
	}
	go doWorker(id, w.in, wg)
	return w
}

func chanDemo() {
	var wg sync.WaitGroup

	var channels [10]worker
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i, &wg)
	}

	wg.Add(10)
	for i := 0; i < 10; i++ {
		channels[i].in <- 'a' + i
	}
	wg.Wait()
}

0x03 select

类似于unix网络编程中的select网络模型。

func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(1500))*time.Microsecond)
			out <- i
			i++
		}
	}()
	return out
}

var c1, c2 = generator(), generator()
w := createWorker(0)
for {
    select {
    case n := <-c1:
        w <- n
    case n := <-c2:
        w <- n
    }
}

但是这种写法不好,原因在于w <- n会被阻塞。我们希望数据的收发可以并发执行,那么可以这么做

var c1, c2 = generator(), generator()
var worker = createWorker(0)
n := 0
hasValue := false
for {
	var activeWorker chan<- int
	if hasValue {
		activeWorker = worker
	}
	select {
	case n = <-c1:
		hasValue = true
	case n = <-c2:
		hasValue = true
	case activeWorker <- n:
		hasValue = false
	}
}

接着按照这种思路,我们需要建立缓冲区,存放我们生产这产生的数据。我们通过一个切片实现这个buf

var values []int
var activeWorker chan<- int
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:]
}

0x04 传统同步机制

  • WaitGroup
  • Mutex
  • Cond

实现一个简单的atomicInt

type atomicInt struct {
	value int
	lock sync.Mutex
}

func (a *atomicInt) increment() {
	a.lock.Lock()
	defer a.lock.Unlock()
	a.value++
}

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

func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Second)
	fmt.Println(a.get())
}

对于临界区的保护,我们可以使用匿名函数

func (a *atomicInt) increment() {
    func () {
        a.lock.Lock()
        defer a.lock.Unlock()
        a.value++
    }()
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值