golang高并发的实现与注意事项

栏目: Go · 发布时间: 5年前

内容简介:一、并发的意义并发的意义就是让golang从语言级别上对并发提供了支持,而且在启动并发的方式上直接添加了语言级的关键字,不必非要按照固定的格式来定义线程函数,也不必因为启动线程的时候只能给线程函数传递一个参数而烦恼。

一、并发的意义

并发的意义就是让 一个程序同时做多件事情 ,其目的只是为了能让程序同时做另一件事情而已,而不是为了让程序运行的更快(如果是多核处理器,而且任务可以分成相互独立的部分,那么并发确实可以让事情解决的更快)。

golang从语言级别上对并发提供了支持,而且在启动并发的方式上直接添加了语言级的关键字,不必非要按照固定的格式来定义线程函数,也不必因为启动线程的时候只能给线程函数传递一个参数而烦恼。

二、并发的启动

go的并发启动非常简单,几乎没有什么额外的准备工作,要并发的函数和一般的函数没有什么区别,参数随意,启动的时候只需要加一个 go 关键之即可,其最精髓的部分在于这些协程(协程类似于线程,但是是更轻量的线程)的调度。

package main

import (
	"fmt"
	"time"
)

func comFunc() {
	fmt.Println("This is a common function.")
}

func main() {
	go comFunc()
	time.Sleep(time.Second * 3)
}

三、协程间的同步与通信

1、sync.WaitGroup

sync包中的WaitGroup实现了一个类似任务队列的结构,你可以向队列中加入任务,任务完成后就把任务从队列中移除,如果队列中的任务没有全部完成,队列就会触发阻塞以阻止程序继续运行,具体用法参考如下代码:

package main

import (
	"fmt"
	"sync"
)

var waitGroup sync.WaitGroup

func Afunction(index int) {
	fmt.Println(index)
	waitGroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	for i := 0; i < 10; i++ {
		waitGroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
		go Afunction(i)
	}
	waitGroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}

2、channel

channel是一种golang内置的类型,英语的直译为"通道",其实,它真的就是一根管道,而且是一个先进先出的数据结构。

我们能对channel进行的操作只有4种:

(1) 创建chennel (通过make()函数)

(2) 放入数据 (通过 channel <- data 操作) 

(3) 取出数据 (通过 <-channel 操作)

channel的3种性质入如下:

(1) channel是一种自动阻塞的管道。 如果管道满了,一个对channel放入数据的操作就会阻塞,直到有某个routine从channel中取出数据,这个放入数据的操作才会执行。相反同理,如果管道是空的,一个从channel取出数据的操作就会阻塞,直到某个routine向这个channel中放入数据,这个取出数据的操作才会执行。 这是channel最重要的一个性质!!!

package main

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 1
	ch <- 1
	ch <- 1 //这一行操作就会发生阻塞,因为前三行的放入数据的操作已经把channel填满了
}
package main

func main() {
	ch := make(chan int, 3)
	<-ch //这一行会发生阻塞,因为channel才刚创建,是空的,没有东西可以取出
}

(2)channel分为有缓冲的channel和无缓冲的channel。两种channel的创建方法如下:

ch := make(chan int) 	//无缓冲的channel,同等于make(chan int, 0)
ch := make(chan int, 5) //一个缓冲区大小为5的channel

无缓冲通道与有缓冲通道的主要区别为:无缓冲通道存取数据是同步的,即如果通道中无数据,则通道一直处于阻塞状态;有缓冲通道存取数据是异步的,即存取数据互不干扰,只有当通道中已满时,存数据操作,通道阻塞;当通道中为空时,取数据操作,通道阻塞。

因此,使用无缓冲的channel时,放入操作和取出操作不能在同一个routine中,而且应该是先确保有某个routine对它执行取出操作,然后才能在另一个routine中执行放入操作,否则会发生死锁现象,示例如下:

package main

import (
	"fmt"
	"sync"
)

var waitGroup sync.WaitGroup //使用wg等待所有routine执行完毕,并输出相应的提示信息

func AFunc(ch chan int) {
	waitGroup.Add(1)
FLAG:
	for {
		select {
		case val := <-ch:
			fmt.Println(val)
			break FLAG
		}
	}
	waitGroup.Done()
	fmt.Println("WaitGroup Done")
}

func main() {

	ch := make(chan int) //无缓冲通道
	execMode := 0        //执行模式 0:先启动并发,正常输出100 1:后启动并发,发生死锁
	switch execMode {
	case 0:
		go AFunc(ch)
		ch <- 100
	case 1:
		ch <- 100
		go AFunc(ch)
	}
	waitGroup.Wait()
	close(ch)
}

使用带缓冲的channel时,因为有缓冲空间,所以只要缓冲区不满,放入操作就不会阻塞,同样,只要缓冲区不空,取出操作就不会阻塞。而且,带有缓冲的channel的放入和取出操作可以用在同一个routine中。但是,一定要注意放入和取出的速率问题,否则也会发生死锁现象,示例如下:

package main

import (
	"fmt"
	"sync"
)

var waitGroup sync.WaitGroup

func AFunc(ch chan int, putMode int) {
	val := <-ch
	switch putMode {
	case 0:
		fmt.Printf("Vaule=%d\n", val)
	case 1:
		fmt.Printf("Vaule=%d\n", val)
		for i := 1; i <= 5; i++ {
			ch <- i * val
		}
	case 2:
		fmt.Printf("Vaule=%d\n", val)
		for i := 1; i <= 5; i++ {
			<-ch
		}
	}

	waitGroup.Done()
	fmt.Println("WaitGroup Done", val)
}

func main() {
	ch := make(chan int, 10)
	putMode := 0 //该模式下,能够正常输出所有数据
	//putMode := 1//当放入速度远大于取数速度时,程序阻塞
	//putMode := 2//当取数速度远大于放数速度时,程序阻塞
	for i := 0; i < 1000; i++ {
		ch <- i
		waitGroup.Add(1)
		go AFunc(ch, putMode)
	}

	waitGroup.Wait()
	close(ch)
}

(3)关闭后的channel可以取数据,但是不能放数据。而且,channel在执行了close()后并没有真的关闭,channel中的数据全部取走之后才会真正关闭。

package main

func main() {
	ch := make(chan int, 5)
	ch <- 1
	ch <- 1
	close(ch)
	ch <- 1 //不能对关闭的channel执行放入操作
        
        // 会触发panic
}
package main

func main() {
	ch := make(chan int, 5)
	ch <- 1
	ch <- 1
	close(ch)
	<-ch //只要channel还有数据,就可能执行取出操作

        //正常结束
}
package main

import "fmt"

func main() {
	ch := make(chan int, 5)
	ch <- 1
	ch <- 1
	ch <- 1
	ch <- 1
	close(ch)  //如果执行了close()就立即关闭channel的话,下面的循环就不会有任何输出了
	for {
		data, ok := <-ch
		if !ok {
			break
		}
		fmt.Println(data)
	}
	
	// 输出:
	// 1
	// 1
	// 1
	// 1
	// 
	// 调用了close()后,只有channel为空时,channel才会真的关闭
}
参考链接: https://studygolang.com/articles/1615

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Rails

Head First Rails

David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99

Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试