Go多线程与channel通信
//1. 低水平实现 生产者-消费者多线程
package main import ( "container/list" "fmt" "math/rand" "runtime" "strconv" "sync" ) type ( Producer interface { produceApple(s *superMakert) //生产苹果 } Consumer interface { buyApple(s *superMakert) //超市买苹果 } superMakert struct { //list包 实现了双向链表 apples *list.List } ProducerImpl struct { Producer //实现生产苹果的接口 } ConsumerImpl struct { Consumer //实现消费苹果的接口 } Apple struct { weight float64 //重量 number int //编号 } ) //实现接口里面的方法 func (p *ProducerImpl) produceApple(s *superMakert) { for { for s.apples.Len() > 0 { //Gosched使当前go程放弃处理器,以让其它go程运行。 //它不会挂起当前go程,因此当前go程未来会恢复执行 runtime.Gosched() } apple := &Apple{ number: rand.Intn(10000), weight: rand.Float64(), } s.apples.PushBack(apple) fmt.Printf("生产了编号为%d,重量为%0.2f的苹果\r\n", strconv.Itoa(apple.number), apple.weight) } } //实现接口里面的方法 func (c *ConsumerImpl) buyApple(s *superMakert) { for { if s.apples.Len() <= 0 { runtime.Gosched() } element := s.apples.Back() apple := element.Value.(*Apple) fmt.Printf("买了编号为%d,重量为%0.2f的苹果\r\n", strconv.Itoa(apple.number), apple.weight) s.apples.Remove(element) } } func main() { //WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。 //每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束 wg := new(sync.WaitGroup) p := new(ProducerImpl) c := new(ConsumerImpl) a := &superMakert{apples: list.New()} wg.Add(2) go func() { //goroutine p.produceApple(a) wg.Done() }() go func() { c.buyApple(a) wg.Done() }() wg.Wait() }
运行程序报错: invalid memory address or nil pointer dereference
错误定位:
if s.apples.Len() <= 0 { runtime.Gosched() }
这里涉及到自旋锁的概念 这里参考 https://www.cnblogs.com/cyyljw/p/8006838.html
自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。
使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。
错误的原因是 : 空指针 。上面代码多个线程同时去访问,没有准确的判断 s.apple.Len() 是否是完全小于0的状态,也就是说在s.apples.Len()为0 的时候程序还是往下执行了导致了空指针。
优化代码为: 用循环一直去判断长度是否为空 但是使用这种方式 在单线程的时候没有意义而且非常消耗CPU的资源
for s.apples.Len() <= 0 { runtime.Gosched() }
运行结果如下: (两个线程同时运行)
总结: 这种方式实现太粗燥。
后续实现高级一点的go 多线程和channel通信
2. 使用channel 实现 多线程
package main import ( "fmt" "math/rand" "strconv" "sync" ) //生产者-消费者 channel 实现 一边生产 一边消费 type ( Producer interface { produceApple(s *superMakert) //生产苹果 } Consumer interface { buyApple(s *superMakert) //超市买苹果 } superMakert struct { //list包 实现了双向链表 //apples *list.List apples chan Apple } ProducerImpl struct { Producer //实现生产苹果的接口 } ConsumerImpl struct { Consumer //实现消费苹果的接口 } Apple struct { weight float64 //重量 number int //编号 } ) //实现接口里面的方法 func (p *ProducerImpl) produceApple(s *superMakert) { for { apple := Apple{ number: rand.Intn(10000), weight: rand.Float64(), } fmt.Printf("生产了编号为%s,重量为%0.2f的苹果\r\n", strconv.Itoa(apple.number), apple.weight) s.apples <- apple } } //实现接口里面的方法 func (c *ConsumerImpl) buyApple(s *superMakert) { for { element, _ := <-s.apples fmt.Printf("买了编号为%s,重量为%0.2f的苹果\r\n", strconv.Itoa(element.number), element.weight) } } func main() { //WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。 //每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束 wg := new(sync.WaitGroup) p := new(ProducerImpl) c := new(ConsumerImpl) a := &superMakert{apples: make(chan Apple)} wg.Add(2) go func() { //goroutine p.produceApple(a) wg.Done() }() go func() { c.buyApple(a) wg.Done() }() wg.Wait() }
3. 多生产多消费
package main import ( "fmt" "math/rand" "strconv" "sync" ) //生产者-消费者 channel 实现 一边生产 一边消费 type ( Producer interface { produceApple(s *superMakert) //生产苹果 } Consumer interface { buyApple(s *superMakert) //超市买苹果 } superMakert struct { //list包 实现了双向链表 //apples *list.List apples chan Apple } ProducerImpl struct { Producer //实现生产苹果的接口 } ConsumerImpl struct { Consumer //实现消费苹果的接口 } Apple struct { weight float64 //重量 number int //编号 } ) //实现接口里面的方法 func (p *ProducerImpl) produceApple(s *superMakert) { for { apple := Apple{ number: rand.Intn(10000), weight: rand.Float64(), } fmt.Printf("生产了编号为%s,重量为%0.2f的苹果\r\n", strconv.Itoa(apple.number), apple.weight) s.apples <- apple } } //实现接口里面的方法 func (c *ConsumerImpl) buyApple(s *superMakert) { for { element, ok := <-s.apples if ok { fmt.Printf("买了编号为%s,重量为%0.2f的苹果\r\n", strconv.Itoa(element.number), element.weight) } else { fmt.Printf("没有买到苹果") } } } func main() { //WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。 //每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束 wg := new(sync.WaitGroup) p := new(ProducerImpl) c := new(ConsumerImpl) a := &superMakert{apples: make(chan Apple, 20)} for i := 0; i < 20; i++ { wg.Add(1) go func() { //goroutine p.produceApple(a) wg.Done() }() } for j := 0; j < 20; j++ { wg.Add(1) go func() { c.buyApple(a) wg.Done() }() } wg.Wait() }