可以让程序并发和并行的运行,增加程序运行速度
并发: 多个线程同时竞争一个位置,竞争到才可以执行,每个时间段只有一个线程在执行
并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。
通俗来说多线程程序在单核CPU上运行是并发,多线程程序在多核CPU上运行就是并行,如果线程数大于CPU核数,则多线程程序在多个CPU上面运行既有并行又有并发
golang 中的主线程:在一个Golang程序的主线程上可以启用多个协程。golang中多协程可以实现并行和并发。
协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang的一个特色是从语言层面原生支持携程,在函数或者方法前面加Go关键词就可以创建一个协程。go的协程就是goroutine.
主线程如果执行完毕后,想要等待携程完成,在退出。
通过sync.WaitGroup 可以实现主线程等待协程执行完毕
var wg sync.WaitGroup
func test() {
for i:= 0; i < 10; i++ {
fmt.Println("test() 你好 gloang", i)
}
wg.Done() // 协程计数器加-1
}
func main(){
wg.Add(1) // 协程计数器 +1
go test() //表示开启一个协程
for( i:= 0; i< 10; i++ {
fmt.Println("main ",i)
}
wg.Wait() //等待协程计数器为0 退出
}
go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核的机器上,调度器会把Go代码同时调度到8个os线程。
package main
import (
"fmt"
"time"
)
func main(){
start := time.Now().Unix()
for num:=2; num < 12000; num ++ {
var flag = true
for i:=2; i < num; i++ {
if num%i == 0 {
flag = false
break
}
}
if flag {
fmt.Println(num,"是素数")
}
}
end := time.Now().Unix()
fmt.Println("程序运行总共用时:",end - start,"ms")
}
package main
import (
"fmt"
"time"
"sync"
)
var wg sync.WaitGroup
func test(n int) {
for num := (n - 1)*30000 + 1; num < n * 30000; num ++ {
var flag = true
for i := 2; i < num; i++ {
if num%i == 0{
flag = false
break
}
}
if flag{
}
}
wg.Done()
}
func main() {
start := time.Now().Unix()
for i := 1; i < 4; i++ {
wg.Add(1)
go test(i)
}
wg.Wait()
end := time.Now().Unix()
fmt.Println("全部执行完毕,共用时", end - start,"ms")
}
管道是Golang在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通讯机制。
Golang的并发模型是CSP,提倡通过通讯共享内存而不是通过共享内存而实现通信。
Go语言中的管道是一种特殊的类型。管道像是一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个管道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
是一种引用类型
var 变量 chan 元素类型
例如:
var ch1 chan int //声明一个传递整型的管道
var ch2 chan bool //声明一个传递布尔类型的管道
var ch3 chan []int //声明一个传递int 切片的管道
声明的管道后需要使用make函数初始化之后才能使用。
make( chan 元素类型, 容量)
管道由发送send, 接收 receive, 关闭close 三种操作。
发送和接收都使用 <- 符号。
发送(将数据放在管道内)
将一个值发送到管道中
ch <- 10
接收(从管道内取值)
从一个管道中接收值
b := <-ch
管道属于引用数据类型
ch1 := make(chan int, 4)
ch1 <- 34
ch1 <- 54
ch1 <- 64
ch2 := ch1
ch2 <- 25
<-ch1
<-ch1
<-ch1
d := <- ch1
fmt.Println(d) // d表示25 证明管道类型是引用类型
管道阻塞
ch6 := make(chan int, 1)
第一种玩法
ch6 <- 34
ch6 <- 64
//报错死锁,这就属于管道阻塞,传进来超容量数据
第二种玩法
ch6 <- 34
ch6 <- 64
m1:= <-ch6
m2:= <-ch6
m3:= <-ch6
//管道中如果没有值了还在取,就会出发阻塞了
如果要循环读取管道,就需要close(ch)关闭管道,否则会造成死锁
通过for 循环管道,管道可以不关闭,但是通过for .......range 获取读取管道,需要关闭
我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收。
//单向管道 只写
ch2 := make(chan<- int ,2)
ch2 <- 10
ch2 <- 12
//单向管道 只读
ch3 := make(<-chan int, 2)
//双向管道
ch4 := make(chan int, 2)
用途: 可以在形参中定义,保证一个函数只能写,另一个函数只能读
在某些场景下,我们需要同时对多个通道接收数据。这个时候我们就可以用到golang中给我们提供的select多路服用。
通常情况通道在接收数据时,如果没有数据可以接收将会发生阻塞。
这种方式虽然可以实现多个管道接收值的需求,但是运行性能会差很多。为了应对这种场景,Go内置了select 关键字,可以同时响应多个管道的操作。
select 的使用类似于switch语句,他有一系列case分支和一个默认的分支。每个case会对应一个管道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。
func main(){
intChan := make(chan int,10)
fot i:=0; i < 10; i++ {
intChan <- i
}
stringChan := make(chan string,5)
for i:=0; i < 10; i++ {
stringChan <- "hello" +fmt.Sprintf("%d",i)
}
for{
select{
case v:= <- intChan:
fmt.Printf("从intChan读取的数据%d\n",v)
case v:= <- stringChan:
fmt.Printf("从stringChan读取的数据%v\n",v)
default:
fmt.Printfln("数据获取完毕")
return
}
}
}
在golang语言中,如果有多个协程的话,其中一个出问题,并不影响其他的协程进行工作。
如果遇到报错的话,可以通过匿名函数和defer进行处理
func test() {
//处理协程异常情况
defer func(){
//捕获函数出现的panic
if err := recover(); err != nil {
fmt.Println("函数出现的错误",err)
}
}
//协程操作
。。。。。
。。。。。
}
互斥锁:是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法:lock和Unlock.lock锁定当前的共享资源,Unlock进行解锁
var mutex sync.Mutex
var count = 0
func test() {
mutex.lock()
count++
fmt.Println("the count is:",count)
mutex.Unlock()
}