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()
}
浙公网安备 33010602011771号