并发编程
基础
- 进程(process):进程是并发执行的程序中分配和管理资源的基本单位。
- 线程(thread):线程是进程的执行单元,是进行调度的实体,是比进程更小的独立运行单位。
- 并发(concurrent):多线程交替操作同一资源。
- 并行(parallel):多个线程同时操作多个资源。
协程goroutine
协程的概念
协程是单线程下的并发,又称为微线程。它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适时机,我们都可以将一个协程切换到另一个协程。英文名Coroutine。
轻量级的线程,独立的栈空间,共享程序堆空间,调度由用户控制,是逻辑态,对资源消耗小。
线程和协程的区别?
线程的切换是一个CPU在不同线程中来回切换,是从系统层面来,不止保存和恢复CPU上下文这么简单,会非常耗费性能。但是协程只是在同一个线程内来回切换不同的函数,只是简单的操作CPU的上下文,所以耗费的性能会大大减小。
多线程并发资源竞争的问题
解决方案一:互斥锁
go
package main
import (
"fmt"
"sync"
"time"
)
var counter int
var lock sync.Mutex
func increment() {
lock.Lock()
for i := 0; i < 100000; i++ {
counter++
}
lock.Unlock()
}
func main() {
for i := 0; i < 20; i++ {
go increment()
}
time.Sleep(3 * time.Second)
fmt.Printf("Final counter value: %d\n", counter)
}
解决方案二:channel
chan 本质就是一个数据结构-队列。
先进先出FIFO的规则,线程安全,多goroutine访问不需要加锁,因为通道本身线程安全。
注意:channel是有类型的,定义存放的类型不能放不同的类型。如果是空接口就能放所有类型。
go
package main
import (
"fmt"
"time"
)
var counter int
var incrementChan = make(chan struct{}, 1)
func increment() {
for i := 0; i < 100000; i++ {
incrementChan <- struct{}{}
counter++
<-incrementChan
}
}
func main() {
for i := 0; i < 20; i++ {
go increment()
}
time.Sleep(3 * time.Second)
fmt.Printf("Final counter value: %d\n", counter)
}
goroutine和channel的综合应用
go
package main
import (
"fmt"
"math/rand"
"time"
)
var intChan chan int
func WriterData(intCh chan int) {
rand.Seed(time.Now().UnixNano())
for i := 1; i < 150; i++ {
var temp int
temp = rand.Intn(4) + 10
intCh <- temp
fmt.Println("write data:", temp)
}
defer close(intCh)
}
func ReadData(intCh chan int, exitChan chan bool) {
var count int
for {
data, ok := <-intCh
if !ok {
break
}
count++
fmt.Println("count:", count, "data:", data)
}
exitChan <- true
defer close(exitChan)
}
func main() {
intChan = make(chan int, 50)
exitChan := make(chan bool, 1)
go WriterData(intChan)
go ReadData(intChan, exitChan)
time.Sleep(time.Second * 1)
fmt.Println("main exit")
}
defer匿名函数捕获Panic
多个协程可能会有panic导致整个程序崩溃。
go
package main
import (
"fmt"
"sync"
)
func recoverFromPanic() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}
func doWork(id int, wg *sync.WaitGroup) {
defer wg.Done()
defer recoverFromPanic()
fmt.Printf("Worker %d started\n", id)
if id == 2 {
panic("Something went wrong!")
}
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go doWork(i, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}
生产者消费者模式
go
package main
import (
"fmt"
"time"
)
// 模拟订单对象
type OrderInfo struct {
id int
}
// 生产订单--生产者
func producerOrder(out chan<- OrderInfo) {
for i := 0; i < 10; i++ {
order := OrderInfo{id: i + 1}
fmt.Println("生成订单,订单ID为:", order.id)
out <- order
}
close(out)
}
// 处理订单--消费者
func consumerOrder(in <-chan OrderInfo) {
for order := range in {
fmt.Println("读取订单,订单ID为:", order.id)
}
}
func main() {
ch := make(chan OrderInfo, 5)
go producerOrder(ch)
go consumerOrder(ch)
time.Sleep(time.Second * 2)
}
协程管道定时任务的应用
1.定时执行某个任务,类似延时消息队列
2.周期性的执行某个任务,类似定期同步某些数据
go
func main() {
fmt.Println("当前时间:", time.Now())
//timer := time.NewTimer(time.Second * 2)
//t := timer.C
t := <-time.After(time.Second * 2)
fmt.Println(t)
}
定时器的停止与重置
timer.Stop()
go
package main
import (
"fmt"
"time"
)
func testChannelTimeout(conn chan int) bool {
// 设置 1 秒的定时器,若在到了1 s ,则进行打印,说明已经超时
timer := time.NewTimer(1 * time.Second)
select {
case <-conn:
if timer.Stop() {
fmt.Println("timer.Stop()")
}
return true
case <-timer.C: // timer 通道超时
fmt.Println("timer Channel timeout!")
return false
}
}
func main() {
ch := make(chan int, 1)
// 若打开如下语句,则可以正常关闭定时器
// 若注释如下语句,则关闭定时器超时
ch <- 1
go testChannelTimeout(ch)
for {
}
}
timer.Reset()
go
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("start", time.Now())
myt := time.NewTimer(time.Second * 2)
// 重置为5秒
myt.Reset(time.Second * 5)
<-myt.C
fmt.Println("end", time.Now())
}