- 多線程同時(shí)執(zhí)行叫做并行
- 并發(fā)就是在不同線程中來(lái)回切換執(zhí)行來(lái)達(dá)到并行的效果就是并發(fā)
- 通過(guò)go可以在當(dāng)前線程中開(kāi)啟一個(gè)協(xié)程
- 保證協(xié)程被執(zhí)行,那么主線程不能掛掉
runtime包中常用的方法
- runtime.Gosched()
- 作用:用于出讓本次的執(zhí)行權(quán)限
- 注意點(diǎn):讓出本次執(zhí)行權(quán)限并不意味著不再執(zhí)行
- 應(yīng)用場(chǎng)景: 讓某些協(xié)程執(zhí)行的次數(shù)少一次, 讓某些協(xié)程執(zhí)行的次數(shù)多一些
- runtime.Goexit()
- 作用:終止調(diào)用它的go程,其他go程不受影響
- 注意點(diǎn):這個(gè)go程將不會(huì)再被調(diào)用
runtime.NumCPU()
獲取本地機(jī)器的邏輯CPU個(gè)數(shù)
runtime.GOMAXPROCS(num)
設(shè)置可同時(shí)執(zhí)行的最大CPU數(shù),并返回上一次設(shè)置的CPU個(gè)數(shù)
注意點(diǎn):Go1.8之后, 系統(tǒng)會(huì)自動(dòng)設(shè)置為最大值,了解------>忘記
互斥鎖
- 當(dāng)使用互斥鎖后,當(dāng)前go程將不被再次調(diào)用
- 案例一:有序打印
/*
需求: 定義個(gè)打印的函數(shù), 可以逐個(gè)的打印傳入字符串的每個(gè)字符
在兩個(gè)協(xié)程中調(diào)用這個(gè)好方法, 并且還要保持輸出的內(nèi)容有序的
*/
var lock = sync.Mutex{}
func printer(str string){
lock.Lock()//添加鎖,如果不添加那么可能執(zhí)行輸出hello也可能執(zhí)行輸出world,那么就是無(wú)序的
for _,val := range str{
fmt.Printf("%c", ch)
time.Sleep(time.Millisecond * 300)
}
lock.Unlock()
}
func main(){
go printer("hello")
go printer("world")
for{
;
}
}
互斥鎖的資源搶奪問(wèn)題
- 注意點(diǎn):如果兩個(gè)go程上了同一把鎖,那么當(dāng)一個(gè)go程被鎖上時(shí),另一個(gè)go程也會(huì)被鎖住
- 案例二:生產(chǎn)者和消費(fèi)者
var lock = sync.Mutex{}
var buff = [10]int
func producer(){
lock.Lock()
rand.Seed(time.Now().UnixNano())
for i:=0; i < 10 ;i++{
num := rand.Intn(100)
fmt.Println("生產(chǎn)者生產(chǎn)了",num)
buff[i] = num
time.Sleep(time.Millisecond * 300)
}
lock.Unlock()
}
func consumer(){
lock.Lock()
for i:=0; i < 10 ;i++{
buff[i] = num
fmt.Println("消費(fèi)者消費(fèi)到了",num)
}
lock.Unlock()
}
func main() {
go producer()
// 我們想要的是, 只有生產(chǎn)者生產(chǎn)了, 我們才能消費(fèi)
// 注意點(diǎn): 在多go程中, 如果生產(chǎn)者生產(chǎn)的太慢, 那么消費(fèi)者就會(huì)消費(fèi)到錯(cuò)誤的數(shù)據(jù)
go consumer()
// 注意點(diǎn): 看上去通過(guò)給生產(chǎn)者以及消費(fèi)者同時(shí)加鎖就能解決, 只有生產(chǎn)完了才能消費(fèi)
// 但是取決于誰(shuí)想執(zhí)行加鎖操作, 所以不完美
for{
;
}
}
在上述案例中,只能一個(gè)生產(chǎn)者對(duì)應(yīng)一個(gè)消費(fèi)者,當(dāng)有第二個(gè)生產(chǎn)者或者第二個(gè)消費(fèi)者時(shí)會(huì)因?yàn)椴l(fā)而產(chǎn)生數(shù)據(jù)混亂锥腻。
在上述案例中,無(wú)法判斷先執(zhí)行消費(fèi)者還是先執(zhí)行生產(chǎn)者,如果先進(jìn)入了調(diào)用者的go程,則會(huì)取不到數(shù)據(jù),就會(huì)發(fā)生數(shù)據(jù)混亂
為了解決上述問(wèn)題,我們可以用管道來(lái)解決這個(gè)問(wèn)題
管道
- 管道是一種數(shù)據(jù)類型,和字典切片很像,不用make函數(shù)創(chuàng)建就使用會(huì)報(bào)錯(cuò)
- 格式: var 變量名稱 chan 數(shù)據(jù)類型 ---------> var myCh chan int
- 作用:在Go語(yǔ)言的協(xié)程中, 一般都使用管道來(lái)保證多個(gè)協(xié)程的同步, 或者多個(gè)協(xié)程之間的通訊
var myCh chan int
myCh = make(chan int, 3)
- 以上代碼創(chuàng)建了一個(gè)容量為3的管道(注意:長(zhǎng)度默認(rèn)為0,添加數(shù)據(jù)以后長(zhǎng)度會(huì)動(dòng)態(tài)變化)
管道的使用
管道寫(xiě)入數(shù)據(jù)
- myChan<-
var myCh chan int
myCh = make(chan int, 3)
myCh<-666 //寫(xiě)入了一個(gè)數(shù)據(jù)666
管道讀取數(shù)據(jù)
- <-myChan
var myCh chan int
myCh = make(chan int, 3)
myCh<-666 //寫(xiě)入了一個(gè)數(shù)據(jù)666
fmt.Println(<-myCh) //讀取了666
管道寫(xiě)入和讀取的注意點(diǎn)
- 沒(méi)有創(chuàng)建管道容量直接使用會(huì)報(bào)錯(cuò)
var myCh chan int
myCh<-666 //報(bào)錯(cuò)
- 管道中沒(méi)有數(shù)據(jù)時(shí)讀取會(huì)報(bào)錯(cuò)
var myCh chan int
myCh = make(chan int, 3)
fmt.Println(<-myCh) //報(bào)錯(cuò)
- 管道已滿,再向管道中寫(xiě)入數(shù)據(jù)會(huì)報(bào)錯(cuò)
var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //寫(xiě)入了一個(gè)數(shù)據(jù)1
myCh<-2 //寫(xiě)入了一個(gè)數(shù)據(jù)2
myCh<-3 //寫(xiě)入了一個(gè)數(shù)據(jù)3
myCh<-4 //報(bào)錯(cuò)
管道的關(guān)閉
- close(管道名稱)
- 注意點(diǎn):管道關(guān)閉以后不能再管道中寫(xiě)入數(shù)據(jù),但是可以在管道中讀取數(shù)據(jù)
管道的遍歷
- 可以使用for循環(huán), 也可以使用 for range循環(huán), 以及死循環(huán)來(lái)遍歷。
- 更推薦使用后兩者母谎。因?yàn)樵谄髽I(yè)開(kāi)發(fā)中, 有可能我們不知道管道中具體有多少條數(shù)據(jù), 所以如果利用for循環(huán)來(lái)遍歷, 那么無(wú)法確定遍歷的次數(shù), 并且如果遍歷的次數(shù)太多, 還會(huì)報(bào)錯(cuò)
for range遍歷
- 注意點(diǎn):在寫(xiě)完數(shù)據(jù)以后必須關(guān)閉管道,否則會(huì)報(bào)錯(cuò)
- 一般企業(yè)開(kāi)發(fā)中,管道數(shù)據(jù)寫(xiě)完之后都會(huì)將管道關(guān)閉
var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //寫(xiě)入了一個(gè)數(shù)據(jù)1
myCh<-2 //寫(xiě)入了一個(gè)數(shù)據(jù)2
myCh<-3 //寫(xiě)入了一個(gè)數(shù)據(jù)3
close()//管道必須關(guān)閉,否則報(bào)錯(cuò)
for v := range myChan{
fmt.Println(v) //先后輸出 1 2 3
}
死循環(huán)遍歷
- 注意點(diǎn): 如果被遍歷的管道沒(méi)有關(guān)閉, 那么會(huì)報(bào)錯(cuò)
- 如果管道被關(guān)閉, 那么會(huì)將true返回給ok, 否則會(huì)將false返回給Ok
var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //寫(xiě)入了一個(gè)數(shù)據(jù)1
myCh<-2 //寫(xiě)入了一個(gè)數(shù)據(jù)2
myCh<-3 //寫(xiě)入了一個(gè)數(shù)據(jù)3
close()//管道必須關(guān)閉,否則報(bào)錯(cuò)
for{
if value,ok:= <-myChan;ok{
fmt.Println(v) //先后輸出 1 2 3
}
}
管道的阻塞現(xiàn)象(重點(diǎn))
單獨(dú)在主線程中操作管道, 寫(xiě)滿了會(huì)報(bào)錯(cuò), 沒(méi)有數(shù)據(jù)去讀取也會(huì)報(bào)錯(cuò)
只要在go程中操作管道, 無(wú)論有沒(méi)有寫(xiě)滿, 無(wú)論有沒(méi)有數(shù)據(jù)都會(huì)發(fā)生管道阻塞的現(xiàn)象
-
阻塞現(xiàn)象(和輸入緩沖區(qū)很相似)
在go程中,如果寫(xiě)滿了,再寫(xiě)入數(shù)據(jù),則不會(huì)報(bào)錯(cuò),等待管道中的數(shù)據(jù)被取出后再添加
在go程中,沒(méi)有數(shù)據(jù)還在被取出,則不會(huì)報(bào)錯(cuò),等待管道中的數(shù)據(jù)被寫(xiě)入后再取出
利用管道阻塞實(shí)現(xiàn)并發(fā)串行
var myChan chan int
myChan = make(chan int,10)
func producer(){
rand.Seed(time.Now().UnixNano())
for i:=0; i < 10 ;i++{
num := rand.Intn(100)
myChan <- num
fmt.Println("生產(chǎn)者生產(chǎn)了",num)
time.Sleep(time.Millisecond * 300)
}
}
func producer2(){
rand.Seed(time.Now().UnixNano())
for i:=0; i < 10 ;i++{
num := rand.Intn(100)
myChan <- num
fmt.Println("生產(chǎn)者生產(chǎn)了",num)
time.Sleep(time.Millisecond * 300)
}
}
func consumer(){
for i:=0; i < 10 ;i++{
num := <-myChan
fmt.Println("消費(fèi)者消費(fèi)到了",num)
}
}
func main() {
go producer()
go producer2()
go consumer()
for{
;
}
}
- 以上代碼可以不止一個(gè)生產(chǎn)者或者消費(fèi)者
利用管道阻塞解決最后寫(xiě)死循環(huán)保證主線程不掛
- 注意點(diǎn): 如果這個(gè)管道在協(xié)程中有被使用,那么編譯器會(huì)認(rèn)為主線程在取數(shù)據(jù)的時(shí)候會(huì)等待協(xié)程中輸入,并不會(huì)報(bào)錯(cuò)
- go程執(zhí)行完后才向管道中填充數(shù)據(jù)
var myCh = make(chan int, 3)
var exitCh = make(chan bool, 1)
go func() {
for i:=0; i<3; i++ {
fmt.Println("生產(chǎn)了數(shù)據(jù)", i)
myCh<-i
}
exitCh<-true
}()
fmt.Println("exitCh之前的代碼")
<-exitCh // 阻塞
fmt.Println("exitCh之后的代碼")
無(wú)緩沖管道
- 無(wú)緩沖管道沒(méi)有容量,在主程中既不可以讀也不可以寫(xiě)
- 無(wú)緩沖區(qū)管道只能存在在go程中,并且如果在同一個(gè)go程中,無(wú)論先讀或者先寫(xiě)都會(huì)阻塞
- 讀寫(xiě)必須都存在,但如果都在主程中會(huì)報(bào)錯(cuò),在同一個(gè)go程中會(huì)阻塞
- 無(wú)緩沖管道如果存在在不同的go程中,先讀或者先寫(xiě)無(wú)所謂
//在go程中可以只讀或者只寫(xiě),會(huì)阻塞
myCh := make(chan int, 0)
go func() {
fmt.Println("123")
myCh<-998
//<-myCh
fmt.Println("abc")
}()
無(wú)緩沖管道解決死循環(huán)
//定義一個(gè)沒(méi)有緩沖的管道
exitCh := make(chan bool)
go func() {
for i:= 0; i < 5; i++ {
myCh<-i
fmt.Println("生產(chǎn)了", i)
}
exitCh<-true
}()
//for{
// ;
//}
//time.Sleep(time.Second)
<-exitCh
單向管道和雙向管道
默認(rèn)情況下所有的管道都是雙向的管道(可讀可寫(xiě))
那么在企業(yè)開(kāi)發(fā)中, 我們可能會(huì)需要將管道作為函數(shù)的參數(shù), 并且還需要限制函數(shù)中如何使用管道,那么這個(gè)時(shí)候我們就可能會(huì)使用單向管道
-
格式:
雙向格式:
var myCh chan int;
myCh = make(chan int, 5)
myCh = make(chan int)單向格式:
var myCh chan<- int; 只寫(xiě)的管道
var myCh <-chan int; 只讀的管道 注意點(diǎn):
1.雙向管道可以賦值給單向管道
2.單向管道賦值給雙向管道會(huì)報(bào)錯(cuò)
單向管道作為函數(shù)參數(shù)
- 增強(qiáng)了代碼的語(yǔ)義
- 管道是地址傳遞
// 定義一個(gè)函數(shù)模擬生產(chǎn)者
func producer(buff chan<- int) {
rand.Seed(time.Now().UnixNano()) // 種隨機(jī)種子
for i:=0; i<5;i++ {
// 產(chǎn)生隨機(jī)數(shù)
num := rand.Intn(100)
fmt.Println("生產(chǎn)者生產(chǎn)了", num)
// 將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū)
buff<-num
//time.Sleep(time.Millisecond * 300)
}
}
// 定義一個(gè)函數(shù)模擬消費(fèi)者
func consumer(buff <-chan int, exitCh chan<- int) {
for i:=0; i<5;i++ {
num := <-buff
fmt.Println("-------消費(fèi)者消費(fèi)到了", num)
}
exitCh<-666
}
func main() {
// 定義一個(gè)數(shù)組模擬緩沖區(qū)
var buff = make(chan int, 5)
var exitCh = make(chan int)
go producer(buff)
go consumer(buff, exitCh)
<-exitCh
fmt.Println("程序結(jié)束了")
//for{
// ;
//}
}
管道是指針類型
var myCh1 chan int = make(chan int, 5)
//fmt.Println(myCh1) // 0xc042094000
//fmt.Printf("%p\n", myCh1) // 0xc042094000
//fmt.Printf("%p\n", &myCh1) // 0xc042082018
myCh1<-1
myCh1<-2
var myCh2 <-chan int
myCh2 = myCh1 // 將雙向的管道轉(zhuǎn)換成單向的管道
// 打印單向管道的長(zhǎng)度和容量
fmt.Println("len", len(myCh2), "cap", cap(myCh2))//len 2 cap 5
fmt.Println(<-myCh2)//1
// 打印雙向管道的長(zhǎng)度和容量
fmt.Println("len", len(myCh1), "cap", cap(myCh1))//len 1 cap 5
select結(jié)構(gòu)
- select選擇結(jié)構(gòu)和switch很像,如果所有case不滿足則會(huì)執(zhí)行default
- 企業(yè)開(kāi)發(fā)中一般不會(huì)使用default,因?yàn)槿菀捉?jīng)常跑進(jìn)default
- 企業(yè)開(kāi)發(fā)中,一般通過(guò)select用于消費(fèi)多個(gè)管道中的數(shù)據(jù)
- 企業(yè)開(kāi)發(fā)中,一般通過(guò)select控制是否超時(shí)
- 企業(yè)開(kāi)發(fā)中,一般通過(guò)select控制退出主線程
- 以下是一個(gè)生產(chǎn)消費(fèi)的案例
// 1.創(chuàng)建一個(gè)管道
myCh1 := make(chan int, 5)
myCh2 := make(chan int, 5)
exitCh := make(chan bool)
// 2.開(kāi)啟一個(gè)協(xié)程生產(chǎn)數(shù)據(jù)
go func() {
//time.Sleep(time.Second * 5) 如果存在這行數(shù)據(jù)會(huì)打印超時(shí)了,不存在則會(huì)正常消費(fèi)
for i := 0; i < 10 ; i++ {
myCh1<-i
fmt.Println("生產(chǎn)者1生產(chǎn)了", i)
}
close(myCh1)
exitCh<-true
}()
go func() {
time.Sleep(time.Second * 5)
for i := 0; i < 10 ; i++ {
myCh2<-i
fmt.Println("生產(chǎn)者2生產(chǎn)了", i)
}
close(myCh2)
}()
for{
select {
case num1 := <-myCh1:
fmt.Println("------消費(fèi)者消費(fèi)了myCh1", num1)
case <-time.After(3):
fmt.Println("超時(shí)了")
return
}
time.Sleep(time.Millisecond)
}
fmt.Println("程序結(jié)束了")
定時(shí)器
- 想使用定時(shí)器需要使用time包
一次性定時(shí)器
NewTimer函數(shù)
- 有一個(gè)Time結(jié)構(gòu)體
- type Timer struct {
C <-chan Time
r runtimeTimer
}
- type Timer struct {
- 作用, 就是讓系統(tǒng)在指定時(shí)間之后, 往Timer結(jié)構(gòu)體的C屬性中寫(xiě)入當(dāng)前的時(shí)間
- NewTimer的函數(shù)接收一個(gè)時(shí)間,代表阻塞多少秒后寫(xiě)入時(shí)間
- 注意:該函數(shù)返回的是一個(gè)Time結(jié)構(gòu)體,要調(diào)用其屬性必須 名稱.屬性
start := time.Now()
fmt.Println(start) //打印當(dāng)前時(shí)間
timer := time.NewTimer(time.Second * 3) // Timer
fmt.Println(<-timer.C) //打印三秒后的時(shí)間
After函數(shù)
- After的函數(shù)接收一個(gè)時(shí)間,代表阻塞多少秒后寫(xiě)入時(shí)間
- 注意:該函數(shù)返回的是一個(gè)Time結(jié)構(gòu)體中C的屬性
start := time.Now()
fmt.Println(start)
timer := time.After(time.Second * 3) // Timer.C
fmt.Println(<-timer)
周期性定時(shí)器
- time包中有一個(gè)NewTicker的函數(shù),接收一個(gè)時(shí)間,表明阻塞多少秒向Time結(jié)構(gòu)體中寫(xiě)入數(shù)據(jù)
- 注意點(diǎn):該函數(shù)會(huì)反復(fù)往結(jié)構(gòu)體中寫(xiě)入數(shù)據(jù),所以需要關(guān)閉,可以用stop函數(shù)進(jìn)行關(guān)閉
start := time.Now()
fmt.Println(start)
ticker := time.NewTicker(time.Second * 2)
for{
fmt.Println(<-ticker.C)
}