Go语言并发编程

Go语言并发编程

    • 1.并发编程
      • 1.1、并发
      • 1.2、goroutine
      • 1.3、加锁
      • 1.4、 channel
        • 1.4.1、实例:
        • 1.4.2、基本语法
        • 1.4.3 、select
        • 1.4.4、单向定义channel
        • 1.4.5、关闭通道channel

1.并发编程

1.1、并发

并发的概念:
并发场景:
 一方面我们需要灵敏响应的图形用户界面,一方面程序还需要执行大量的运算或者IO密
集操作,而我们需要让界面响应与运算同时执行。
 当我们的Web服务器面对大量用户请求时,需要有更多的“Web服务器工作单元”来分别
响应用户。
 我们的事务处于分布式环境上,相同的工作单元在不同的计算机上处理着被分片的数据。
 计算机的CPU从单内核(core)向多内核发展,而我们的程序都是串行的,计算机硬件的
能力没有得到发挥。
 我们的程序因为IO操作被阻塞,整个程序处于停滞状态,其他IO无关的任务无法执行。
并发优点:
 并发能更客观地表现问题模型;
 并发可以充分利用CPU核心的优势,提高程序的执行效率;
 并发能充分利用CPU与其他硬件设备固有的异步性。

1.2、goroutine

go开启一个线程使程序并发执行

func main() {

	/*
		go开启并发执行
		输出:
			开始
			结束
	*/
	fmt.Println("开始")
	for i := 0; i < 10; i++ {
		go addAB(i, i) //开启并发执行
	}
	fmt.Println("结束")
}

func addAB(a, b int) int {
	z := a + b
	fmt.Println("打印z:", z)
	return z
}

结果分析:go是并发执行会开启一个新的线程,当主函数结束时,所有线程将会关闭,此时addAB中方法还未执行,所以没有打印语句。如果想打印语句可以使用sleep循环等待来等待所有线程执行完毕。

1.3、加锁

使用锁使并行的线程按顺序执行

func main() {

	/*
		go开启并发执行
		输出:
			开始
			打印: 2
			打印: 3
			打印: 4
			打印: 5
			打印: 6
			结束

	*/
	fmt.Println("开始")
	lock := &sync.Mutex{}
	for i := 0; i < 5; i++ {
		go count(lock)
	}

	//以下防止循环count()未执行完相当于sleep()
	for {
		if i > 5 {
			break
		}
	}
	fmt.Println("结束")

}

var i = 1

func count(lock *sync.Mutex) {
	lock.Lock()
	i++
	fmt.Println("打印:", i)
	lock.Unlock()
}

结论:在上面的例子中,我们在5个goroutine中共享了变量i。每个goroutine执行完成后,
将i的值加1。因为5个goroutine是并发执行的,所以我们还引入了锁,也就是代码中的
lock变量。每次对i的操作,都要先将锁锁住,操作完成后,再将锁打开。在主函数中,使用for
循环来不断检查i的值(同样需要加锁)。当其值达到5时,说明所有goroutine都执行完
毕了,这时主函数返回,程序退出

1.4、 channel

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

1.4.1、实例:
func Count(ch chan int) {
	fmt.Println("ch<-", 1)
	ch <- 1
}
func main() {
	chs := make([]chan int, 100)
	for i := 0; i < 100; i++ {
		chs[i] = make(chan int)
		go Count(chs[i])
	}
	for _, ch := range chs {
		fmt.Println(<-ch) 
	}
}
1.4.2、基本语法
声明:var chanName chan ElementType
实例:
//声明一个int类型的channel
var ch chan int
//声明一个map类型为bool的channel
var ch map[string] chan bool

定义:chanName := make(chan ElementType)
实例
ch := make(chan int)

定义:chanName := make(chan ElementType, number)
实例
ch := make(chan int,10)//定义一个缓冲区为10的int类型channel,跟以上区别:即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。

读出:  <- chanName 
实例:
	value := <- ch

读出2:
实例
for i := range c { 
 fmt.Println("Received:", i) 
}

写入:chanName <-
实例:
	ch <- value
	
1.4.3 、select

select用法与swithc相似,(select {case 条件:语句 case 条件:语句 default:语句}),select开始,case随机选择进行判断,如果成立执行该case中数据,否则重新随机选择下一个case,最后都不成立走默认。使用select机制可以避免永久等待的问题。

实例1、

func main() {
	ch1 := make(chan int, 1)
	ch2 := make(chan int, 1)

	ch1 <- 1

	select {
	case <-ch1:
		fmt.Println("ch1读取数据成功")
	case <-ch2:
		fmt.Println("ch2读取数据成功")
	default:
		fmt.Println("读取数据失败")
	}
}

实例2、随机存储1和0

func main() {
	ch := make(chan int, 1)
	for {
		select {
		case ch <- 0:
		case ch <- 1:
		}
		i := <-ch
		fmt.Println("Value received:", i)
	}
}
1.4.4、单向定义channel

单向定义channel顾名思义就是只能发送和接收数据。用于更好的控制权限,权限读、写、读和写

func main() {
	ch1 := make(chan int, 10)
	ch2 := <-chan int(ch1) //<-chan 定义只能读取不能写入
	ch3 := chan<- int(ch1) //chan<- 定义只能写入不能读取

	var ch4 <-chan int //<-chan 定义只能读取不能写入
	var ch5 chan<- int //chan<- 定义只能写入不能读取

	ch1 <- 1
	<-ch1

	ch3 <- 2
	ch5 <- 3

	<-ch2 //读取报错,不知道为什么,如果有人知道请评论下,谢谢
	<-ch4
}
1.4.5、关闭通道channel
func main() {
	ch := make(chan int, 10)
	ch <- 3
	close(ch) //Go语言内置的关闭close()函数

	x, ok := <-ch //如果返回值是false则表示ch已经被关闭。
	
	println(x, ok)
}

注意:channel关闭之后,不能继续赋值,但是仍然可以从channel中读取剩余的数据,直到数据全部读取完成。

你可能感兴趣的:(golang,笔记,go,后端,开发语言)