"白昼会边长,照亮心脏,让万物生长。"
一、Golang进阶
我们对golang的语法进行了一定的了解后,也算是入门了。本节的进阶篇围绕三个方向展开,Goroutine 、 Channel 、Sync。
如何理解并行与并发?
并行是指“并排行走”或“同时实行或实施”。
在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行) 。
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
下面两张图就可以区别并发与并行;
并发:
并行:
同样,随着技术的迭代升级也有了并行+并发:
看完上面的图解。
并发的实质是针对单核CPU,它的交替调度不同任务的能力
并行的是追是针对多核CPU,它指的是多个核心同时执行多个任务的能力。
由此,单核 CPU 只能并发,无法并行。并行,只存在于多核CPU的硬件条件下。
而在多核CPU中,并发并行都会同时存在,这是提高CPU处理任务能力的重要手段
(1)Goroutine
协程,又叫做轻量级线程。这在Golang这样的编程语言中特别流行。
Go协程的特点:
1.独立的栈空间
2.共享堆空间
3. 协程调度由用户控制(进程的控制是有操作系统控制,程序员不能控制)
不管是父进程创建的子进程,还是在linuxPOSIX提供的线程库。其中的管理都是交由操作系统。因此,在程序执行的过程中,总会存在执行状态的切换(用户到内核,内核到用户)。但是,golang中的协程完完全全解决了这个问题。
协程:用户态,轻量级线程。
线程:内核态,线程可以跑多个协程。
创建一个线程栈大概需要 1MB 左右,而协程栈大概只需要几 KB或者几十KB。
package main
import (
"fmt"
"time"
)
func hello(i int) {
fmt.Println("go() to hello", i)
}
func HelloRotinue() {
for i := 0; i < 5; i++ {
go hello(i)
}
time.Sleep(time.Second)
}
func main() {
HelloRotinue()
for i := 0; i < 5; i++ {
fmt.Println("main() to hello", i)
time.Sleep(time.Second)
}
}
我们在调用打印hello的函数前面加上关键字"go"。表面,我们起一个协程来调用这个函数。
但我们此时没有设置HelloRotinue为线程,因此是等待该函数调用完时,才开始进行主线程main。
此时,只需要在该函数前加上go,这两个执行流就不会处在所谓的"阻塞"状态,而是各管各的。
什么是CSP?
CSP理念: 以通信的方式来共享内存。Go 的并发哲学,依赖于 CSP 模型。
大多数编程语言,在如何实现并发的问题上,采用的都是基于线程和内存同步访问控制。而Go 的并发编程的模型则用 goroutine 和 channel 来替代。
Goroutine 和线程类似,channel 和 mutex (用于内存同步访问控制的互斥锁)类似。
我们在linux处着重讲了,实现进程间的本质,就是让每个进程(线程)可看到同一份公共资源。而这个而资源,也叫做临界区。
通过共享内存实现通信;
通过通信共享内存;
(2)Channel
make(chan+元素类型,[缓冲大小])
例如:
无缓冲通道 make(chan int)
有缓冲通道 make(chan int,2)
比如,现在我们要执行完成一个任务。协程1发送0~9数字,协程2拿到数字后计算它们的平方,并发送给主线程,并在主线程处打印。
最后我们也可以得到结果。
(3)Sync
sync(意指Synchronize,即“同步”)为UNIX操作系统的标准系统调用,功能为将内核文件系统缓冲区的所有数据。
sync:
Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.
包同步提供了基本的同步原语,如互斥锁。除了Once和WaitGroup类型之外,大多数类型都是供低级库例程使用的。更高级别的同步最好通过信道和通信来完成。
并发安全 Lock
我们期待五个协程对x进行2000循环地自增加1
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithOutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithOutLock()
}
fmt.Println("WithOutLock: ", x)
time.Sleep(time.Second)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("WithLock: ", x)
}
WaitGroup
这个类里有三个比较重要的函数;
我们简简单单地用五个协程分别打印。
我们对代码进行一定的改进。让打印后的结果进行阻塞。
总结:
以上就是golang面向并发编程的相关介绍。本篇涉及的三个方面,协程(Gorotinue),信道(Channel)通过通信实现共享内存,sync关键字,支持同步互斥的并发安全操作。
本篇也就到此结束了~感谢你的阅读
祝你好运,向阳而生