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()
}

  

  

 

posted @ 2019-02-26 23:48  wpgraceii  阅读(556)  评论(0编辑  收藏  举报