当前位置:网站首页>【Golang】快速复习指南QuickReview(八)——goroutine
【Golang】快速复习指南QuickReview(八)——goroutine
2022-06-23 19:45:00 【DDGarfield】
goroutine是Golang特有,类似于线程,但是线程是由操作系统进行调度管理,而goroutine是由Golang运行时进行调度管理的用户态的线程。
1.C#的线程操作
1.1 创建线程
static void Main(string[] args)
{
Thread thread = new Thread(Count);
thread.IsBackground = true;
thread.Start();
for (int i = 0; i < 10; i++)
Console.Write("x\n");
}
static void Count()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i); ;
}
}
1.2 向线程传参
Thread构造函数有两个参数ParameterizedThreadStart与ThreadStart
public delegate void ParameterizedThreadStart(object? obj);
public delegate void ThreadStart();
没错,一个是无参委托,,一个是有参委托且参数类型为object,因此我们用以创建线程的方法参数需为object类型,然后在内部对object类型参数做一个转换:
static void Main(string[] args)
{
Thread thread = new Thread(Count);
thread.IsBackground = true;
thread.Start(100);
for (int i = 0; i < 10; i++)
Console.Write("x\n");
}
static void Count(object times)
{
int count = (int)times;
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i); ;
}
}
当然最简单的还是直接使用lambda表达式
static void Main(string[] args)
{
Thread thread = new Thread(()=>{
Count(100);
});
thread.IsBackground = true;
thread.Start();
for (int i = 0; i < 10; i++)
Console.Write("x\n");
}
static void Count(object times)
{
int count = (int)times;
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i); ;
}
}
注意:使用Lambda表达式可以很简单的给Thread传递参数,但是线程开始后,可能会不小心修改了被捕获的变量,这要多加注意。比如循环体中,最好创建一个临时变量。
1.3 线程安全与锁
从单例模式来看线程安全:
class Student
{
private static Student _instance =new Student();
private Student()
{
}
static Student GetInstance()
{
return _instance;
}
}
**单例模式,我们的本意是始终保持类实例化一次然后保证内存中只有一个实例。**上述代码中在类被加载时,就完成静态私有变量的初始化,不管需要与否,都会实例化,这个被称为
饿汉模式的单例模式。这样虽然没有线程安全问题,但是这个类如果不使用,就不需要实例化。然后便有了下面的写法:需要时,才实例化
class Student
{
private static Student _instance;
private Student()
{
}
static Student GetInstance()
{
if (_instance == null)
_instance = new Student();
return _instance;
}
}
上述代码,调用时判断静态私有变量是否为空,然后再赋值。这个其实就有一个线程安全的问题:多线程调用GetInstance(),当同时多个线程执行时,条件_instance == null可能会同时都满足。这样_instance就完成了多次实例化赋值操作,就引出了我们的锁Lock
private static Student _instance;
private static readonly object locker = new object();
private Student()
{
}
static Student GetInstance()
{
lock (locker)
{
if (_instance == null)
_instance = new Student();
}
return _instance;
}
- 第一个线程运行,就会加锁
Lock - 第二个线程运行,首先检测到locker对象为"加锁"状态(是否还有线程在
lock内,未执行完成),该线程就会阻塞等待第一个线程解锁 - 第一个线程执行完
lock体内代码,解锁,第二个线程才会继续执行
上面看似已经完美了,但是多线程情况下,每次都要经历,检测(是否阻塞),上锁,解锁,其实是很影响性能,我们本来的目的是返回单一实例即可。**我们在检测locker对象是否加锁之前,如果实例已经存在,那么后续工作是没必要做的。**所以就有了下面的 双重检验:
private static Student _instance;
private static readonly object locker = new object();
private Student()
{
}
static Student GetInstance()
{
if (_instance == null)
{
lock (locker)
{
if (_instance == null)
_instance = new Student();
}
}
return _instance;
}
1.4 Task
线程有两种工作类型:
- CPU-Bound:计算密集型,花费大部分时间执行CPU密集型工作的操作,这种工作类型永远也不会让线程处在等待状态。
- IO-Bound:I/O密集型,花费大部分时间等待某事发生的操作,一直等待着,导致线程进入等待状态的工作类型。比如通过http请求对资源的访问。
对于IO-Bound的操作,时间花费主要是在I/O上,而在CPU上几乎是没有花费时间。对于此,更推荐使用Task编写异步代码,而对于CPU-Bound和IO-Bound的异步代码不是我们本篇的重点,博主将大概介绍一下Task的优势:
- 不再干等:: 以
HttpClient使用异步代码请求GetStringAsync为例,这显然是一个I/O-Bound,最终会对操作系统本地网络库进行调用,系统API调用,比如发起请求的socket,但是这个时间长短并不是由代码决定,他取决硬件,操作系统,网络情况。控制权会返回给调用者,我们可以做其他操作,这让系统能处理更多的工作而不是等待 I/O 调用结束。直到await去获得请求结果。 - 让调用者不再干等: 对于
CPU-Bound,没有办法避免将一个线程用于计算,因为这毕竟是一个计算密集型的工作。但是使用Task的异步代码(async与await)不仅可以与后台线程交互,还可以让调用者继续响应(可以并发执行其他操作)。同上,直到遇到await时,异步方法都会让步于调用方。
2.Golang的goroutine
2.1 启动goroutine
Golang中启动一个goroutine没有C#的线程那么麻烦,只需要在调用方法的前面加上关键字go.
func main(){
go Count(100)
}
func Count(times int) {
for i := 0; i < times; i++ {
fmt.Printf("%v\n", i)
}
}
2.2 goroutine的同步
在C#中的任务(Task)可以使用Task.WhenAll来等待Task对象完成。而在Golang中就看起来简单粗暴,它将使用sync包的WaitGroup,然后像个花名册一样登记造册:
- 启动 - 计数+1
- 执行完毕 - 计数-1
- 完成
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
//登记造册
wg.Add(2)
go Count(100)
go Count(100)
//等待所有登记的goroutine都结束
wg.Wait()
fmt.Println("执行完成")
}
func Count(times int) {
//执行完成
defer wg.Done()
for i := 0; i < times; i++ {
fmt.Printf("%v\n", i)
}
}
2.3 channel通道
一个goroutine发送特定值到另一个goroutine的通信机制就是channel
2.3.1 channel声明
channel是引用类型
var 变量 chan 元素类型
chan 元素类型跟struct,interface一样,就是一种类型。后面的元素类型限定了通道具体存储类型。
2.3.2 channel初始化
声明后的channel是空值nil,需要初始化才能使用。
var ch chan int
ch=make(chan int,5) //5为设定的缓冲大小,可选参数
2.3.3 channel操作
操作三板斧:
- send - 发送
ch<-100
箭头从值指向通道
- receive - 接收
value:=<-ch
i, ok := <-ch1 // 通道关闭后再取值ok=false
//或者
for i := range ch1 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
箭头从通道指向变量
- close - 关闭
close(ch)
2.3.3 缓冲与无缓冲
ch1:=make(chan int,5) //5为设定的缓冲大小,可选参数
ch2:=make(chan int)
无缓冲的通道,无缓冲的通道只有在接收值的时候才能发送值。
- 只往通道传值,不从通道接收,就会出现
deadlock - 只从通道接收,不往通道发送,也会发生阻塞
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
有缓冲的通道,可以有效缓解无缓冲通道的尴尬,但是通道装满,上面的尴尬依然存在。
2.3.4 单向通道
限制通道在函数中只能发送或只能接收,单向通道粉墨登场,单向通道的使用是在函数的参数中,也没有引入新的关键字,只是简单的改变的箭头的位置:
chan<- int 只写不读
<-chan int 只读不写
函数传参及任何赋值操作中可以将双向通道转换为单向通道,反之,不行。
2.3.5 多路复用
package main
import "fmt"
func main() {
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Printf("第%v次,x := <-ch,从通道中读取值%v", i+1, x)
fmt.Println()
case ch <- i:
fmt.Printf("第%v次,执行ch<-i", i+1)
fmt.Println()
}
}
}
第1次,执行ch<-i
第2次,x := <-ch,从通道中读取值0
第3次,执行ch<-i
第4次,x := <-ch,从通道中读取值2
第5次,执行ch<-i
第6次,x := <-ch,从通道中读取值4
第7次,执行ch<-i
第8次,x := <-ch,从通道中读取值6
第9次,执行ch<-i
第10次,x := <-ch,从通道中读取值8
Select多路复用的规则:
- 可处理一个或多个channel的发送/接收操作。
- 如果多个
case同时满足,select会随机选择一个。 - 对于没有
case的select{}会一直等待,可用于阻塞main函数。
2.5 并发安全与锁
goroutine是通过channel通道进行通信。不会出现并发安全问题。但是,实际上还是不能完全避免操作公共资源的情况,如果多个goroutine同时操作这些公共资源,可能就会发生并发安全问题,跟C#的线程一样,锁的出现就是为了解决这个问题:
2.5.1 互斥锁
互斥锁,这个就跟C#的锁的机制是一样的,一个goroutine访问,另外一个就只能等待互斥锁的释放。同样需要sync包:
sync.Mutex
var lock sync.Mutex
lock.Lock()//加锁
//操作公共资源
lock.Unlock()//解锁
2.5.2 读写互斥锁
互斥锁是完全互斥的,如果是读多写少,大部分goroutine都在读,少量的goroutine在写,这时并发读是没必要加锁的。使用时,依然需要sync包:
sync.RWMutex
读写锁分为两种:
- 读锁
- 写锁
import (
"fmt"
"sync"
)
var (
lock sync.Mutex
rwlock sync.RWMutex
)
rwlock.Lock() // 加写锁
//效果等同于互斥锁
rwlock.Unlock() // 解写锁
rwlock.RLock() //加读锁
//可读不可写
rwlock.RUnlock() //解读锁
2.6* sync.Once
goroutine的同步我们使用过sync.WaitGroup
Add(count int)计数器累加,在调用goroutine外部执行,由开发人员指定- Done() 计数器-1,在goroutine内部执行
- Wait() 阻塞 直至计数器为0
除此之外还有一个sync.Once,顾名思义,一次,只执行一次。
func (o *Once) Do(f func()) {}
var handleOnce sync.Once
handleOnce.Do(函数)
sync.Once其实内部包含一个互斥锁和一个布尔值,互斥锁保证布尔值和数据的安全,而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的,并且初始化操作也不会被执行多次。
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/x86),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
2.7* sync.Map
Golang的map不是并发安全的。sync包中提供了一个开箱即用的并发安全版map–sync.Map。
开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
var m = sync.Map{}
m.Store("四川", "成都")
m.Store("成都", "高新区")
m.Store("高新区", "应龙南一路")
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
v, ok := m.Load("成都")
if ok {
fmt.Println(v)
}
fmt.Println()
value, loaded := m.LoadOrStore("陕西", "西安")
fmt.Println(value)
fmt.Println(loaded)
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
//【取值与创建】
//存在就加载,不存在就添加
value1, loaded1 := m.LoadOrStore("四川", "成都")
fmt.Println(value1)
fmt.Println(loaded1)
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
//【删除】
//加载并删除 key存在
value2, loaded2 := m.LoadAndDelete("四川")
fmt.Println(value2)
fmt.Println(loaded2)
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
//加载并删除 key 不存在
value3, loaded3 := m.LoadAndDelete("北京")
fmt.Println(value3)
fmt.Println(loaded3)
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
m.Delete("成都") //内部是调用的LoadAndDelete
//【遍历】
m.Range(func(key interface{}, value interface{}) bool {
fmt.Printf("k=:%v,v:=%v\n", key, value)
return true
})
fmt.Println()
k=:四川,v:=成都
k=:成都,v:=高新区
k=:高新区,v:=应龙南一路
高新区
西安
false
k=:四川,v:=成都
k=:成都,v:=高新区
k=:高新区,v:=应龙南一路
k=:陕西,v:=西安
成都
true
k=:四川,v:=成都
k=:成都,v:=高新区
k=:高新区,v:=应龙南一路
k=:陕西,v:=西安
成都
true
k=:陕西,v:=西安
k=:成都,v:=高新区
k=:高新区,v:=应龙南一路
<nil>
false
k=:成都,v:=高新区
k=:高新区,v:=应龙南一路
k=:陕西,v:=西安
k=:高新区,v:=应龙南一路
k=:陕西,v:=西安
2.8 单例模式
综上,sync.Once其实内部包含一个互斥锁和一个布尔值,这个布尔值就相当于C#单例模式下的双重检验的第一个判断。所以在golang中可以利用sync.Once实现单例模式:
package singleton
import (
"sync"
)
type singleton struct {}
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
2.9* 原子操作
代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全。Golang中原子操作由内置的标准库sync/atomic提供。由于场景较少,就不做介绍,详细操作请自行查阅学习。
边栏推荐
- Netseer: stream event telemetry notes for programmable data plane
- 深入理解和把握数字经济的基本特征
- Open source SPL redefines OLAP server
- NAACL 2022 Findings | 字节提出MTG:多语言文本生成数据集
- 如何利用数仓创建时序表
- Idea console displays Chinese garbled code
- 1、 Summary and introduction
- SAP实施项目上的内部顾问与外部顾问,相互为难还是相互成就?
- Are there conditions for making new bonds? Is it safe to make new bonds
- Helix QAC is updated to 2022.1 and will continue to provide high standard compliance coverage
猜你喜欢

Zabbix监控- Aruba AP运行数据

Add two factor authentication, not afraid of password disclosure, let alone 123456

Syntax of SQL union query (inline, left, right, and full)

游戏资产复用:更快找到所需游戏资产的新方法

Rstudio 1.4 software installation package and installation tutorial

盘点四种WiFi加密标准:WEP、WPA、WPA2、WPA3

Elastricearch's fragmentation principle of the second bullet

JS高级程序设计第 4 版:生成器的学习

LeetCode 1079. 活字印刷

直播回顾 | 云原生混部系统 Koordinator 架构详解(附完整PPT)
随机推荐
OHOS LTS 3.0移植到RaspberryPi 4B
金九银十,靠这个细节,offer拿到手软!
Programmable, protocol independent software switch (read the paper)
直播回顾 | 云原生混部系统 Koordinator 架构详解(附完整PPT)
国内期货开户怎么开?哪家期货公司开户更安全?
Ugeek's theory 𞓜 application and design of observable hyperfusion storage system
QGIS import WMS or WMTs
如何在Microsoft Exchange 2010中安装SSL证书
SAP实施项目上的内部顾问与外部顾问,相互为难还是相互成就?
Technology sharing | wvp+zlmediakit realizes streaming playback of camera gb28181
Virtp4 notes
Chaos engineering, learn about it
LeetCode 1079. 活字印刷
【Golang】快速复习指南QuickReview(一)——字符串string
打新债有条件吗 打新债安全吗
What are the requirements for new bonds? Is it safe to play new bonds
NAACL 2022 Findings | 字节提出MTG:多语言文本生成数据集
Leaders of Hangcheng street, Bao'an District and their delegation visited lianchengfa for investigation
科班出身,结果外包都不要
Timertasks notes