当前位置: 首页 > news >正文

服务网站建设方案上海专业的网站建设公司

服务网站建设方案,上海专业的网站建设公司,网站上面添加地图,温州网站开发goruntine(协程) 每一个并发的执行单元叫做一个goruntine#xff0c;要编写一个并发任务#xff0c;可以在函数名前加go关键字#xff0c;就能使这个函数以协程的方式运行#xff0c; 如#xff1a;go 函数名#xff08;函数参数#xff09;、 如果函数有返回值…goruntine(协程) 每一个并发的执行单元叫做一个goruntine要编写一个并发任务可以在函数名前加go关键字就能使这个函数以协程的方式运行 如go 函数名函数参数、 如果函数有返回值返回值会被忽略。所以当你用go关键字后主进程调用函数的时候函数的返回值就没有和主进程进行数据交换而只能用channel 线程和协程的区别 区别1线程有固定的栈基本都是2MB都是固定分配的这个栈用于保存局部变量在函数切换时使用。对于协程来说固定的栈可能会导致资源浪费go采用了动态收缩扩张收缩的策略初始化为2KB最大可扩张到1GB 区别2线程切换要陷入内核进行上下文切换而协程在用户态由协程调度器完成不用陷入内核 demo:协程的基本使用 package mainimport (fmttime )func Task1() {for {fmt.Println(time.Now().Format(10:48:00), 正在处理task1的任务)time.Sleep(time.Second * 3)} }func Task2() {for {fmt.Println(time.Now().Format(10:48:00), 正在处理task2的任务)time.Sleep(time.Second * 1)}}func main() {go Task1()go Task2()for {fmt.Println(time.Now().Format(10:48:00), 正在处理主进程的任务)time.Sleep(time.Second * 2)} } demo2 package mainimport (fmttime )/* 当一个程序启动时其主函数即在一个单独的goroutine中运行我们称之为main goroutine。下面这个程序运行后不会有任何输出因为运行go task1(),程序立刻返回到main函数main函数后没有任何代码逻辑程序判断为执行完毕终止所有协程。 如果要让task1函数执行可以在main函数加上一些等待逻辑确定子协程执行完毕后结束main函数。如sleep() */func task1() {for {fmt.Println(time.Now().Format(10:48:00), 正在处理task1的任务)time.Sleep(time.Second * 3)} }func main() {go task1()time.Sleep(time.Second * 100) } demo3: 使用匿名函数创建goruntine package mainimport (fmttime )/* 使用匿名函数创建goruntine,格式如下 go func(参数列表{函数体 }(调用参数列表*/func main() {go func() {for {fmt.Println(time.Now().Format(11:17:00), 正在处理task1的任务)time.Sleep(time.Second * 3)}}()time.Sleep(time.Second * 100) } channel go中推荐使用channel作为goruntine之间同步和通信的手段 常见写法var channelName chan T 含义用chan关键字声明了一个channel变量并指定了传输的数据类型T channel的发送和接收channel作为一个队列收发数据时按先入先出的原则同一时刻内只能允许一个goruntine访问channel来发送和接受数据 发送channel-val 含义表示将var发送到channel中如果channel中数据被填满之后会阻塞当前goruntine 接受val-channel 含义表示从channel中读取数据赋值给var上如果channel中没有数据会阻塞读取的goruntine直到有数据被放到channel中。 注可以在读取channel时立刻返回如 var,ok:-channel 此时检查ok是否为true用来判断是否读到了有效数据。 初始化ch:make(chan T,sizeofChan) 注 在创建channel时要指定channel传输的数据类型长度是可选的。如果不指定数据类型会导致向channel发送数据的goruntine被阻塞直到数据被读取如果指定了长度表示是有缓冲的channel在缓冲区未满时发送给数据不会被阻塞。无论channel是否携带缓冲区读取的goroutine都会被阻塞直到channel中有数据可被读取。 demo下面通过一个例子演示goruntine和channel配合创建一个channel用于在两个goruntine中收发数据其中一个goruntine从命令行读取输入发送到channel中另一个goruntine循环的从channel中读取数据并输出 import (bufiofmtos )//从channel中读数据进行打印 func printInput(ch chan string) {for val1 : range ch {//读取到结束符合if val1 EOF {break}fmt.Printf(input is: %s\n, val1)} }func main() {//创建一个无缓冲的channelch : make(chan string)go printInput(ch)//从命令行读取输入scanner : bufio.NewScanner(os.Stdin)for scanner.Scan() {var1 : scanner.Text()//将读到的命令行发送到channelch - var1if var1 EOF {fmt.Println(End the game)break}}//关闭channeldefer close(ch) } 运行结果 i am cc input is: i am cc eof input is: eof EOF End the game 上述例子的代码中我们通过for:range语法从channel中循环读取数据当channel中没有数据时printInput的goroutine将会被阻塞。 代码的最后我们还通过defer close(ch)关闭了创建的channel,需要注意的是在channel关闭后不允许再往通道中放入数据不然会抛出panic; 而再从关闭的channel读取数据或者之前从channel读取数据并被阻塞的goroutine将会接收到零值直接返回。 demo2带缓冲区的channel package mainimport (fmttime )/* 带缓冲区的channel创建channel时指定了channel的长度那么channel将会拥有缓冲区goroutine在缓冲区未满时往channel发送数据将不会被阻塞。*/func consume(ch chan int) {//线程休息30s再从channel中读数据time.Sleep(time.Second * 30)-ch }func main() {//创建一个长度为2的channelch : make(chan int, 2)go consume(ch)ch - 0ch - 1//发送数据不被阻塞fmt.Println(I am free!)ch - 2fmt.Println(I can not go there within 100s!)time.Sleep(time.Second*2) } 运行结果30s之内只输出I am free! 这段时间内是阻塞的30s之后协程从channel读数据后再往里面发送数据2是不阻塞的所以输出了I can not go there within 30s! I am free! I can not go there within 30s! 除了声明双向channel,.我们还可以声明单向的channel,即只能从channel发送数据或者只能从channel中读取数据代码如下所示 ch : make(chan T) // 声明只能发送的通道 var chInput chan - int ch// 声明只能读取的通道 var chOutput int - chan ch demo3select使用 运行结果 get value 0 from ch1 get value 1 from ch1 get value 10 from ch2 get value 11 from ch2 get value 12 from ch2 get value 2 from ch1 get value 3 from ch1 get value 13 from ch2 get value 4 from ch1 get value 5 from ch1 get value 6 from ch1 get value 14 from ch2 get value 15 from ch2 get value 16 from ch2 get value 7 from ch1 get value 8 from ch1 get value 9 from ch1 get value 17 from ch2 get value 18 from ch2 get value 19 from ch2 time out 当然每次运行结果都可能不一样因为 goroutine 调度的不确定性。 上述代码中我们通过select多路复用分别从chl和ch2中读取数据如果多个case语句中的ch同时到达那么select将会运行一个伪随机算法随机选择一个case。在最后的case中我们设定可接受的最大时长为2s,如果时间超过2s,将从select中退出。 由于channel的阻塞是无法被中断的所以这是一种有效地从阻塞的channel中超时返回的小技巧。 sync包 go中除了使用channel进行goruntine之间的同步和通信操作外还可以使用sync包提供的并发工具类主要有7种 Mutex 互斥锁RWMutex 读写锁WaitGroup 并发等待组Map 并发安全字典Cond 同步等待条件Once 只执行一次Pool 临时对象池 这节讲解一下互斥锁的用法sync.Mutex能够保证同一个时间段内只有一个goruntine持有锁这就能保证在某一个时间段内有且仅有一个goruntine访问共享资源其他想申请锁的goruntine将会被阻塞直到锁被释放然后重新争抢锁的持有权 demo使用Sync.Mutex控制多个goruntine串行执行 package mainimport (fmtsynctime )func main() {var lock sync.Mutexgo func() {//加锁lock.Lock()defer lock.Unlock()fmt.Println(func1 get lock at time.Now().String())time.Sleep(time.Second)fmt.Println(func1 release lock at time.Now().String())}()time.Sleep(time.Second / 10)go func() {lock.Lock()defer lock.Unlock()fmt.Println(func2 get lock at time.Now().String())time.Sleep(time.Second)fmt.Println(func2 release lock at time.Now().String())}()//等待所有goruntine执行完毕time.Sleep(time.Second * 4) } 读写锁的用法 为了保证同一时间段有多个goruntine访问同一资源要满足以下条件同一时间段只能有一个goruntine获取到写锁同一时间段可以有任意多个goruntine获取到读锁同一时间段只能存在读锁或写锁读锁和写锁互斥 demosync.RWMutex 允许多读和单写的案例 package mainimport (fmtstrconvsynctime )var rwLock sync.RWMutexfunc main() {//获取读锁for i : 0; i 5; i {go func(i int) {rwLock.RLock()defer rwLock.RUnlock()fmt.Println(read func strconv.Itoa(i) get rlock at time.Now().String())time.Sleep(time.Second)}(i)}time.Sleep(time.Second / 10)//获取写锁for i : 0; i 5; i {go func(i int) {rwLock.Lock()defer rwLock.Unlock()fmt.Println(write func strconv.Itoa(i) get wlock at time.Now().String())time.Sleep(time.Second)}(i)}//保证所有的goruntine执行结束time.Sleep(time.Second * 10) } 运行结果 read func4 get rlock at2023-03-14 14:24:02.5203537 0800 CST m0.003223301 read func1 get rlock at2023-03-14 14:24:02.5203537 0800 CST m0.003223301 read func2 get rlock at2023-03-14 14:24:02.5203537 0800 CST m0.003223301 read func3 get rlock at2023-03-14 14:24:02.5203537 0800 CST m0.003223301 read func0 get rlock at2023-03-14 14:24:02.5203537 0800 CST m0.003223301 write func0 get wlock at2023-03-14 14:24:03.5513528 0800 CST m1.034222401 write func4 get wlock at2023-03-14 14:24:04.5535884 0800 CST m2.036458001 write func2 get wlock at2023-03-14 14:24:05.5637904 0800 CST m3.046660001 write func1 get wlock at2023-03-14 14:24:06.5747967 0800 CST m4.057666301 write func3 get wlock at2023-03-14 14:24:07.5782438 0800 CST m5.061113401 从输出的结果可以看出在写锁没有被获取时所有read goroutine可以同时申请到读锁如read func0到read func4几乎在同一时间点获取到读锁 而申请写锁的goroutine必须等到没有任何的读锁和其他写锁存在时才能申请成功如write func0需要等到readfunc最后一个goroutine释放读锁才能申请到写锁其他申请写锁的write func需要等待前一个write func释放写锁后才能重新争抢写锁它们获取到写锁的时间大约相差1秒这刚好是 每个write func 持有写锁的时间 demosync.WaitGroup使用 package mainimport (fmtstrconvsynctime )/* sync.WaitGroup使用 使用sync.WaitGroup的goruntine会等预设好数量的goruntine都提交执行结束后才会继续往下执行代码*/func main() {var waitGroup sync.WaitGroup//设置等待goruntine数量为5waitGroup.Add(5)for i : 0; i 5; i {go func(i int) {fmt.Println(work strconv.Itoa(i) is done at time.Now().String())time.Sleep(time.Second)waitGroup.Done()}(i)}waitGroup.Wait()fmt.Println(all works are done at time.Now().String()) } demosync.Map的使用 package mainimport (fmtstrconvsync )/* sync.Map的使用: Go语言中原生的Map并不是并发安全的在多个goroutine同时往Map中添加数据时可能会导致部分添加数据的丢失为了避免这种情况 在Go语言l.9版本之前我们需要结合sync.RWMutex和Map实现并发安全的字典。 Go语言1.9之后提供了sync.Map,相对于原生的Map,它只提供以下接口:*/var syncMap sync.Map var waitGroup sync.WaitGroupfunc main() {routineSize : 5//设置等待goruntine数量为5waitGroup.Add(routineSize)//并发添加数据for i : 0; i routineSize; i {go addNumber(i * 10)}//开始等待waitGroup.Wait()var size int//统计数量syncMap.Range(func(key, value interface{}) bool {sizefmt.Println(key-value pair is , key, value)return true})fmt.Println(syncMap current size is strconv.Itoa(size))//获取键为0的值value, ok : syncMap.Load(0)if ok {fmt.Println(key 0 has value , value, )} }func addNumber(begin int) {//往syncMap中放入数据for i : begin; i begin3; i {syncMap.Store(i, i)}//通知数据已添加完毕waitGroup.Done() } 补充学习资料关于golang的并发同步与安全 - 9ong 可以补充学习 并发同步之sync.WaitGroup golang经常会有多协程并行的场景而我们又需要这些协程全部结束后继续完成main协程的后续行为这个时候我们需要一个谁来统计所有协程的运行结果并通知main协程不能仅仅依靠timeout、sleep的方式硬编写main协程等待其他协程时间无法完美预估其他协程最晚完成时间。 所以就有了sync.WaitGroupsync.WaitGroup内部维护着一个计数器计数器的值可以增加和减少。比如启动了5个并发任务时就将计数器值增加5每个任务完成时通过调用Done()方法将计数器减1当计数器值为0时表示所有并发任务已经完成通过调用Wait()来等待并发任务执行完达到main协程知道所有协程任务完成的效果。 package mainimport (fmtsync )var wg sync.WaitGroupfunc goodjob(num int) {defer wg.Done()fmt.Println(num, good job.) } func main() {for i : 0; i 5; i {wg.Add(1)go goodjob(i)}fmt.Println(main goroutine do something.)wg.Wait()fmt.Println(main goroutine end.) } 使用sync.WaitGroup后的输出 main goroutine do something. 1 good job. 0 good job. 2 good job. 3 good job. 4 good job. main goroutine end. 如果不使用WaitGroup将不会看到goodjob协程的输出打印只有main协程的输出因为main协程并不会去等待其他协程而是先执行并退出main协程一旦退出main协程其他协程也会被销毁在销毁前还来不及执行因为需要一些时间给GMP调度 并发加载之sync.once 可能不同协程会加载相同的静态资源在高并发场景下这些资源其实只需要加载一次就可以比如配置、关闭一次通道、连接远端资源等sync包提供了Once解决方案一次只执行一次的解决方案。 package mainimport (errorsfmtsync )type subConfig struct {url stringsrc stringdesc string }var configs map[string]subConfig var loadOnce sync.Oncefunc loadConfigs() {configs map[string]subConfig{icons: loadIcons(),ads: loadAds(),banners: loadBanners(),} }func getConfig(key string) (res subConfig, err error) {//这里如果我们判断nil的方式来规避并发的话是不安全的// if configs nil {// loadConfigs()// }//所以我们换成了sync.Once的Do方法来控制并发安全及避免通过锁的机制来控制资源并发访问带来的性能问题loadOnce.Do(loadConfigs)res, ok : configs[key]if !ok {errors.New(key invalid.)}return }func loadIcons() subConfig {//可能从文件、缓存、数据库等资源获取icons : subConfig{src: http://www.9ong.com/xxx.png,url: http://www.9ong.com/,desc: 9ong icon desc,}return icons }func loadBanners() subConfig {//可能从文件、缓存、数据库等资源获取banners : subConfig{src: http://www.9ong.com/xxx.png,url: http://www.9ong.com/,desc: 9ong banner desc,}return banners }func loadAds() subConfig {//可能从文件、缓存、数据库等资源获取ads : subConfig{src: http://www.9ong.com/xxx.png,url: http://www.9ong.com/,desc: 9ong ads desc,}return ads }func main() {//并发调用getConfigads, err : getConfig(ads)if err ! nil {fmt.Println(err)} else {fmt.Println(ads)} } 在getConfig函数中我们通过判断configs这个全局变量是否为nil决定是否重新加载configs数据在这种情况下就会出现即使判断了configs不是nil也不意味着configs变量初始化完成了。考虑到这种情况我们能想到的办法就是添加互斥锁保证初始化加载configs全局资源变量的时候不会被其他的协程读写但是这样做又会引发性能问题。 sync.Once内部包含一个互斥锁和一个布尔值互斥锁保证布尔值和数据的安全而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。 所以getConfig函数改造后通过loadOnce.Do(loadConfigs)来实现互斥加载。 解决了并发加载问题还不会因为多次加载浪费时间和空间及引入锁等机制带来的性能问题。 并发安全与锁之sync.mutex package mainimport (fmtsync )var love int32 10000 //公共资源love var wg sync.WaitGroupfunc getLove() {defer wg.Done()for i : 0; i 1000; i {love love - 1}fmt.Println(love left:, love) } func main() {for children : 0; children 10; children {wg.Add(1)go getLove()}wg.Wait()fmt.Println(love is:, love) } 由于启动了10个协程这些协程存在竞争love资源的可能多试几次或把初始值和循环数调大点可能会出现以下的输出其实也就是电商里说的超卖问题按照逻辑love最后应该为0但实际情况是love还剩下3045 I:\src\go\src\helogo run test.go love left: 7763 love left: 8763 love left: 9000 love left: 6763 love left: 6452 love left: 5452 love left: 5220 love left: 4220 love left: 4045 love left: 3045 love is: 3045 超卖的问题在于并发不安全并发时对临界资源的读写不安全我们需要考虑在读写临界资源时将并发/并行转换成串行通常采用加锁的机制。 互斥锁(完全互斥)是一种常用的控制共享资源访问的方法它能够保证同时只有一个协程可以访问共享资源。golang中使用sync包的Mutex类型来实现互斥锁 package mainimport (fmtsync )var love int32 10000 //公共资源love var wg sync.WaitGroup var mu sync.Mutexfunc getLove() {defer wg.Done()mu.Lock()for i : 0; i 1000; i {love love - 1}mu.Unlock()fmt.Println(love left:, love) } func main() {for children : 0; children 10; children {wg.Add(1)go getLove()}wg.Wait()fmt.Println(love is:, love) } 前面我们说互斥锁是完全互斥的但是有很多实际的场景下是读多写少的当并发去读取一个资源不涉及资源修改的时候是没有必要加锁的如果使用完全互斥锁的话会导致读多的协程会堵塞等待锁的释放很影响效率这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。 读写锁分为读锁和写锁当一个协程获取读锁之后其他的协程如果是获取读锁会继续获得锁如果是获取写锁就会等待当一个协程获取写锁之后其他的协程无论是获取读锁还是写锁都会等待。 也就是说读写锁互斥读读不会互斥协程的读锁不应其他协程的读锁获取但影响写锁的获取而写锁影响所有协程对该资源锁的获取。 编码如何处理呢 我们只要对共享资源的读加读锁Rlock()对共享资源的写加写锁lock() var rwlock sync.RWMutexfunc read(){rwlock.RLock()//read love actionrwlock.RUnlock() }func write(){rwlock.Lock()//writen love actioinrwlock.Unlock() } 并发安全之sync.map golang内置map不是并发安全的很好理解map只是一种资源的数据结构而已所以作为共享资源其本身当然也是并发不安全的对于map中key、value的操作也需要做并发安全处理比如加锁。 虽然加锁可以解决但golang也提供了一套并发安全的map操作类sync.Map其内置了Store、Load、LoadOrStore、Delete、Range等操作方法这些方法都是并发安全的可以理解为原子操作。 package mainimport (fmtstrconvsync )var sm sync.Map{} var wg sync.WaitGroup{}func setMap(i int) {key : strconv.Itoa(i)sm.Store(key, i)value, _ : sm.Load(key)fmt.Printf(k:%v,v:%v\n, key, value)wg.Done()}func main() {for i : 0; i 20; i {wg.Add(1)go setMap(i)}wg.Wait() } 并发安全之atomic 针对基本数据类型我们还可以使用原子操作来保证并发安全因为原子操作是Go语言提供的方法它在用户态就可以完成因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。 package mainimport (fmtsyncsync/atomic )var love int32 10000 //公共资源love var wg sync.WaitGroup var mu sync.Mutexfunc getLove() {defer wg.Done()for i : 0; i 1000; i {love love - 1}fmt.Println(love left:, love) } func mutexGetLove() {defer wg.Done()mu.Lock()for i : 0; i 1000; i {love love - 1}mu.Unlock()fmt.Println(love left:, love) } func atomicGetLove() {defer wg.Done()for i : 0; i 1000; i {atomic.AddInt32(love, -1)}fmt.Println(love left:, love) } func main() {for children : 0; children 10; children {wg.Add(1)// go getLove() //普通并发不安全// go mutexGetLove() //互斥锁并发安全但性能开销大go atomicGetLove() //原子操作并发安全性能优于互斥锁机制}wg.Wait()fmt.Println(love is:, love) } atomic包提供了底层的原子级内存操作虽然其对于同步算法的实现很有效果但除了某些特殊的底层应用我们建议使用channel或者sync.包实现同步会更好些。
http://www.w-s-a.com/news/583127/

相关文章:

  • 哪里有做网站企业seo关键词优化
  • 上海金山网站建设公司手机淘宝客网站怎么做的
  • 网站开发需要公司做网站费用计入什么科目
  • 网站优化有哪些类型免费制作app的傻瓜软件
  • 如何做网站咨询wordpress get
  • 企业网站建设网站做网站用别人的图片
  • 站长统计代码个人网站源代码
  • 求推荐专门做借条的网站公众号排版编辑器
  • 动态做网站网站开发语言查询 蔡学镛
  • 莆田网站建设创意自助建站英文
  • cms系统创建静态网站龙岗网站建设哪家好
  • 自己做的网站被封了邢台规划局网站建设
  • 网站建设项目合同wordpress主题没法用
  • 个旧市哪里有做网站wordpress内页php页面
  • 程序员接活的平台网站互联网平台建设方案
  • 网站安全建设模板深圳企业管理咨询公司
  • 做网站 还是淘宝店wordpress分类链接后加
  • wordpress腾讯云 COSseo内容优化心得
  • 特价旅游机票网站建设i营销
  • 如何成立网站深圳创业项目
  • 建设商业网站惠州网站建设推荐乐云seo
  • 如何申请免费域名做网站免费推广神器
  • 自媒体人专用网站安岳网站建设
  • 特乐网站建设做网站推广要多少钱
  • 山东省建设安全生产协会网站义乌跨境电商公司前十名
  • 做网站优化就是发文章吗起飞页自助建站平台的特点
  • 做网站还是做app好慈溪机械加工网
  • 上传下载文件网站开发的php源码腾讯企点
  • 给分管领导网站建设情况汇报怎么写网络运营的岗位职责及任职要求
  • 电线电缆技术支持中山网站建设广告设计培训学校有哪些