GoLang之Concurrency再讨论

本文通过示例探讨了Go语言中goroutine的并发特性及其安全性问题,包括如何实现真正的并发、解决非并发安全问题以及原子操作的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0 goroutine是否并发的问题

GoLang通过go关键字实现并发操作(真的并发吗?),一个最简单的并发模型:

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "math/rand"  
  6.     "time"  
  7. )  
  8.   
  9. func routine(name string, delay time.Duration) {  
  10.     t0 := time.Now()  
  11.     fmt.Println(name, " start at ", t0)  
  12.   
  13.     // 停留xxx秒  
  14.     time.Sleep(delay)  
  15.   
  16.     t1 := time.Now()  
  17.     fmt.Println(name, " end at ", t1)  
  18.   
  19.     // 计算时间差  
  20.     fmt.Println(name, " lasted ", t1.Sub(t0))  
  21.   
  22. }  
  23.   
  24. func main() {  
  25.   
  26.     // 生成随机种子, 类似C语言中的srand((unsigned)time(0))生成随机种子  
  27.     rand.Seed(time.Now().Unix())  
  28.   
  29.     // To convert an integer number of units to a Duration, multiply  
  30.     fmt.Println(time.Duration(5) * time.Second)  
  31.   
  32.     var name string  
  33.     for i := 0; i < 3; i++ {  
  34.         name = fmt.Sprintf("go_%02d", i) // 生成ID  
  35.   
  36.         // 生成随机等待时间, 从0-4秒  
  37.         // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.  
  38.         go routine(name, time.Duration(rand.Intn(5))*time.Second)  
  39.     }  
  40.   
  41.     // 让主进程停住, 不然主进程退了, goroutine也就退了  
  42.     var input string  
  43.     fmt.Scanln(&input)  
  44.     fmt.Println("done")  
  45. }  
  46.   
  47. /* 
  48. output: 
  49.  
  50. mba:test gerryyang$ ./rand_t 
  51. 5s 
  52. go_00  start at  2013-12-28 13:25:04.460768468 +0800 HKT 
  53. go_01  start at  2013-12-28 13:25:04.460844141 +0800 HKT 
  54. go_02  start at  2013-12-28 13:25:04.460861337 +0800 HKT 
  55. go_02  end at  2013-12-28 13:25:04.460984329 +0800 HKT 
  56. go_02  lasted  122.992us 
  57. go_01  end at  2013-12-28 13:25:05.462003787 +0800 HKT 
  58. go_01  lasted  1.001159646s 
  59. go_00  end at  2013-12-28 13:25:07.461884807 +0800 HKT 
  60. go_00  lasted  3.001116339s 
  61.  
  62. done 
  63. */  

关于goroutine是否真正并发的问题,耗子叔叔这里是这样解释的:

引用:

关于goroutine,我试了一下,无论是Windows还是Linux,基本上来说是用操作系统的线程来实现的。不过,goroutine有个特性,也就是说,如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。这并不是真正的并发,如果你要真正的并发,你需要在你的main函数的第一行加上下面的这段代码:

  1. import runtime  
  2. runtime.GOMAXPROCS(n)  

本人使用go1.2版本在Linux64,2.6.32内核环境下测试,在上述代码中再添加一个死循环的routine,可以验证上述的逻辑。在没有设置GOMAXPROCS参数时,多个goroutine会出现阻塞的情况;设置GOMAXPROCS参数时,下面的几个routine可以正常执行不会被阻塞。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "math/rand"  
  6.     "time"  
  7.     "runtime"  
  8. )  
  9.   
  10. func routine(name string, delay time.Duration) {  
  11.     t0 := time.Now()  
  12.     fmt.Println(name, " start at ", t0, ", sleep:", delay)  
  13.   
  14.     // 停留xxx秒    
  15.     time.Sleep(delay)  
  16.   
  17.     t1 := time.Now()  
  18.     fmt.Println(name, " end at ", t1)  
  19.   
  20.     // 计算时间差    
  21.     fmt.Println(name, " lasted ", t1.Sub(t0))  
  22.   
  23. }  
  24.   
  25. func die_routine() {  
  26.     for {  
  27.     // die loop  
  28.     }  
  29. }  
  30.   
  31. func main() {  
  32.   
  33.     // 实现真正的并发  
  34.     runtime.GOMAXPROCS(4)  
  35.   
  36.     fmt.Println("set runtime.GOMAXPROCS")  
  37.   
  38.     // 生成随机种子, 类似C语言中的srand((unsigned)time(0))生成随机种子    
  39.     rand.Seed(time.Now().Unix())  
  40.   
  41.     // To convert an integer number of units to a Duration, multiply    
  42.     fmt.Println(time.Duration(5) * time.Second)  
  43.   
  44.     // die routine  
  45.     go die_routine()  
  46.   
  47.     var name string  
  48.     for i := 0; i < 3; i++ {  
  49.         name = fmt.Sprintf("go_%02d", i) // 生成ID    
  50.   
  51.         // 生成随机等待时间, 从0-4秒    
  52.         // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.    
  53.         go routine(name, time.Duration(rand.Intn(5))*time.Second)  
  54.     }  
  55.   
  56.     // 让主进程停住, 不然主进程退了, goroutine也就退了    
  57.     var input string  
  58.     fmt.Scanln(&input)  
  59.     fmt.Println("done")  
  60. }  

1 goroutine非并发安全性问题

这是一个经常出现在教科书里卖票的例子,启了5个goroutine来卖票,卖票的函数sell_tickets很简单,就是随机的sleep一下,然后对全局变量total_tickets作减一操作。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "time"  
  6.     "math/rand"  
  7.     "runtime"  
  8. )  
  9.   
  10. var total_tickets int32 = 10  
  11.   
  12. func sell_tickets(i int) {  
  13.     for {  
  14.         // 如果有票就卖  
  15.         if total_tickets > 0 {  
  16.             time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)  
  17.             // 卖一张票  
  18.             total_tickets--  
  19.             fmt.Println("id:", i, " ticket:", total_tickets)  
  20.         } else {  
  21.             break  
  22.         }  
  23.     }  
  24. }  
  25.   
  26. func main() {  
  27.   
  28.     // 设置真正意义上的并发  
  29.     runtime.GOMAXPROCS(4)  
  30.   
  31.     // 生成随机种子  
  32.     rand.Seed(time.Now().Unix())  
  33.   
  34.     // 并发5个goroutine来卖票  
  35.     for i := 0; i < 5; i++ {  
  36.         go sell_tickets(i)  
  37.     }  
  38.   
  39.     // 等待线程执行完  
  40.     var input string  
  41.     fmt.Scanln(&input)  
  42.     // 退出时打印还有多少票  
  43.     fmt.Println(total_tickets, "done")  
  44. }  
  45. /* 
  46. output: 
  47.  
  48. id: 1  ticket: 8 
  49. id: 0  ticket: 8 
  50. id: 0  ticket: 7 
  51. id: 2  ticket: 5 
  52. id: 4  ticket: 6 
  53. id: 4  ticket: 3 
  54. id: 3  ticket: 3 
  55. id: 1  ticket: 1 
  56. id: 0  ticket: 2 
  57. id: 3  ticket: -1 
  58. id: 2  ticket: -1 
  59. id: 1  ticket: -2 
  60. id: 4  ticket: -3 
  61.  
  62. -3 done 
  63. */  

上述例子没有考虑并发安全问题,因此需要加一把锁以保证每个routine在售票的时候数据同步。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "time"  
  6.     "math/rand"  
  7.     "runtime"  
  8.     "sync"  
  9. )  
  10.   
  11. var total_tickets int32 = 10  
  12. var mutex = &sync.Mutex{}  
  13.   
  14. func sell_tickets(i int) {  
  15.   
  16.     for total_tickets > 0 {  
  17.   
  18.         mutex.Lock()  
  19.         // 如果有票就卖  
  20.         if total_tickets > 0 {  
  21.             time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)  
  22.             // 卖一张票  
  23.             total_tickets--  
  24.             fmt.Println("id:", i, " ticket:", total_tickets)  
  25.         }  
  26.         mutex.Unlock()  
  27.     }  
  28. }  
  29.   
  30. func main() {  
  31.   
  32.     // 设置真正意义上的并发  
  33.     runtime.GOMAXPROCS(4)  
  34.   
  35.     // 生成随机种子  
  36.     rand.Seed(time.Now().Unix())  
  37.   
  38.     // 并发5个goroutine来卖票  
  39.     for i := 0; i < 5; i++ {  
  40.         go sell_tickets(i)  
  41.     }  
  42.   
  43.     // 等待线程执行完  
  44.     var input string  
  45.     fmt.Scanln(&input)  
  46.     // 退出时打印还有多少票  
  47.     fmt.Println(total_tickets, "done")  
  48. }  
  49. /* 
  50. output: 
  51.  
  52. id: 0  ticket: 9 
  53. id: 0  ticket: 8 
  54. id: 0  ticket: 7 
  55. id: 0  ticket: 6 
  56. id: 0  ticket: 5 
  57. id: 0  ticket: 4 
  58. id: 0  ticket: 3 
  59. id: 0  ticket: 2 
  60. id: 0  ticket: 1 
  61. id: 0  ticket: 0 
  62.  
  63. 0 done 
  64. */  

2 并发情况下的原子操作问题

go语言也支持原子操作。关于原子操作可以参考耗子叔叔这篇文章《 无锁队列的实现 》,里面说到了一些CAS – CompareAndSwap的操作。下面的程序有10个goroutine,每个会对cnt变量累加20次,所以,最后的cnt应该是200。如果没有atomic的原子操作,那么cnt将有可能得到一个小于200的数。下面使用了atomic操作,所以是安全的。

  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "sync/atomic"  
  6.     "time"  
  7. )  
  8.   
  9. func main() {  
  10.   
  11.     var cnt uint32 = 0  
  12.   
  13.     // 启动10个goroutine  
  14.     for i := 0; i < 10; i++ {  
  15.         go func() {  
  16.             // 每个goroutine都做20次自增运算  
  17.             for i := 0; i < 20; i++ {  
  18.                 time.Sleep(time.Millisecond)  
  19.                 atomic.AddUint32(&cnt, 1)  
  20.             }  
  21.         }()  
  22.     }  
  23.   
  24.     // 等待2s, 等goroutine完成  
  25.     time.Sleep(time.Second * 2)  
  26.     // 取最终结果  
  27.     cntFinal := atomic.LoadUint32(&cnt)  
  28.   
  29.     fmt.Println("cnt:", cntFinal)  
  30. }  
  31. /* 
  32. output: 
  33.  
  34. cnt: 200 
  35. */  




转帖自http://blog.youkuaiyun.com/delphiwcdj/article/details/17630863

内容概要:本文围绕直流微电网中带有恒功率负载(CPL)的DC/DC升压转换器的稳定控制问题展开研究,提出了一种复合预设性能控制策略。首先,通过精确反馈线性化技术将非线性不确定的DC转换器系统转化为Brunovsky标准型,然后利用非线性扰动观测器评估负载功率的动态变化和输出电压的调节精度。基于反步设计方法,设计了具有预设性能的复合非线性控制器,确保输出电压跟踪误差始终在预定义误差范围内。文章还对比了多种DC/DC转换器控制技术如脉冲调整技术、反馈线性化、滑模控制(SMC)、主动阻尼法和基于无源性的控制,并分析了它们的优缺点。最后,通过数值仿真验证了所提控制器的有效性和优越性。 适合人群:从事电力电子、自动控制领域研究的学者和工程师,以及对先进控制算法感兴趣的研究生及以上学历人员。 使用场景及目标:①适用于需要精确控制输出电压并处理恒功率负载的应用场景;②旨在实现快速稳定的电压跟踪,同时保证系统的鲁棒性和抗干扰能力;③为DC微电网中的功率转换系统提供兼顾瞬态性能和稳态精度的解决方案。 其他说明:文中不仅提供了详细的理论推导和算法实现,还通过Python代码演示了控制策略的具体实现过程,便于读者理解和实践。此外,文章还讨论了不同控制方法的特点和适用范围,为实际工程项目提供了有价值的参考。
内容概要:该论文介绍了一种名为偏振敏感强度衍射断层扫描(PS-IDT)的新型无参考三维偏振敏感计算成像技术。PS-IDT通过多角度圆偏振光照射样品,利用矢量多层光束传播模型(MSBP)和梯度下降算法迭代重建样品的三维各向异性分布。该技术无需干涉参考光或机械扫描,能够处理多重散射样品,并通过强度测量实现3D成像。文中展示了对马铃薯淀粉颗粒和缓步类动物等样品的成功成像实验,并提供了Python代码实现,包括系统初始化、前向传播、多层传播、重建算法以及数字体模验证等模块。 适用人群:具备一定光学成像和编程基础的研究人员,尤其是从事生物医学成像、材料科学成像领域的科研工作者。 使用场景及目标:①研究复杂散射样品(如生物组织、复合材料)的三维各向异性结构;②开发新型偏振敏感成像系统,提高成像分辨率和对比度;③验证和优化计算成像算法,应用于实际样品的高精度成像。 其他说明:PS-IDT技术相比传统偏振成像方法具有明显优势,如无需干涉装置、无需机械扫描、可处理多重散射等。然而,该技术也面临计算复杂度高、需要多角度数据采集等挑战。文中还提出了改进方向,如采用更高数值孔径(NA)物镜、引入深度学习超分辨率技术等,以进一步提升成像质量和效率。此外,文中提供的Python代码框架为研究人员提供了实用的工具,便于理解和应用该技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值