并发的概念:
并发场景:
一方面我们需要灵敏响应的图形用户界面,一方面程序还需要执行大量的运算或者IO密
集操作,而我们需要让界面响应与运算同时执行。
当我们的Web服务器面对大量用户请求时,需要有更多的“Web服务器工作单元”来分别
响应用户。
我们的事务处于分布式环境上,相同的工作单元在不同的计算机上处理着被分片的数据。
计算机的CPU从单内核(core)向多内核发展,而我们的程序都是串行的,计算机硬件的
能力没有得到发挥。
我们的程序因为IO操作被阻塞,整个程序处于停滞状态,其他IO无关的任务无法执行。
并发优点:
并发能更客观地表现问题模型;
并发可以充分利用CPU核心的优势,提高程序的执行效率;
并发能充分利用CPU与其他硬件设备固有的异步性。
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循环等待来等待所有线程执行完毕。
使用锁使并行的线程按顺序执行
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都执行完
毕了,这时主函数返回,程序退出
channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或
多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调
用函数时的参数传递行为比较一致,比如也可以传递指针等。
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)
}
}
声明: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
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)
}
}
单向定义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
}
func main() {
ch := make(chan int, 10)
ch <- 3
close(ch) //Go语言内置的关闭close()函数
x, ok := <-ch //如果返回值是false则表示ch已经被关闭。
println(x, ok)
}
注意:channel关闭之后,不能继续赋值,但是仍然可以从channel中读取剩余的数据,直到数据全部读取完成。