Go语言教程之边写边学:Goroutines和Channels练习:吸烟者问题

假设一支香烟需要三种成分来制作和吸烟:烟草、纸张和火柴。一张桌子周围有三个吸烟者,每个吸烟者都有三种成分中的一种——一个吸烟者有无限供应的烟草,另一个有纸,第三个有火柴。第四方,拥有无限供应的所有东西,随机选择一个吸烟者,并将香烟所需的用品放在桌子上。被选中的吸烟者吸烟,该过程应无限期重复。

 

示例代码:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

const (
	paper = iota
	grass
	match
)

var smokeMap = map[int]string{
	paper: "paper",
	grass: "grass",
	match: "match",
}

var names = map[int]string{
	paper: "Sandy",
	grass: "Apple",
	match: "Daisy",
}

type Table struct {
	paper chan int
	grass chan int
	match chan int
}

func arbitrate(t *Table, smokers [3]chan int) {
	for {
		time.Sleep(time.Millisecond * 500)
		next := rand.Intn(3)
		fmt.Printf("Table chooses %s: %s\n", smokeMap[next], names[next])
		switch next {
		case paper:
			t.grass <- 1
			t.match <- 1
		case grass:
			t.paper <- 1
			t.match <- 1
		case match:
			t.grass <- 1
			t.paper <- 1
		}
		for _, smoker := range smokers {
			smoker <- next
		}
		wg.Add(1)
		wg.Wait()
	}
}

func smoker(t *Table, name string, smokes int, signal chan int) {
	var chosen = -1
	for {
		chosen = <-signal // blocks

		if smokes != chosen {
			continue
		}

		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		select {
		case <-t.paper:
		case <-t.grass:
		case <-t.match:
		}
		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		time.Sleep(10 * time.Millisecond)
		select {
		case <-t.paper:
		case <-t.grass:
		case <-t.match:
		}
		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		fmt.Printf("%s smokes a cigarette\n", name)
		time.Sleep(time.Millisecond * 500)
		wg.Done()
		time.Sleep(time.Millisecond * 100)
	}
}

const LIMIT = 1

var wg *sync.WaitGroup

func main() {
	wg = new(sync.WaitGroup)
	table := new(Table)
	table.match = make(chan int, LIMIT)
	table.paper = make(chan int, LIMIT)
	table.grass = make(chan int, LIMIT)
	var signals [3]chan int
	// three smokers
	for i := 0; i < 3; i++ {
		signal := make(chan int, 1)
		signals[i] = signal
		go smoker(table, names[i], i, signal)
	}
	fmt.Printf("%s, %s, %s, sit with \n%s, %s, %s\n\n", names[0], names[1], names[2], smokeMap[0], smokeMap[1], smokeMap[2])
	arbitrate(table, signals)
}

 

输出:

Sandy, Apple, Daisy, sit with
paper, grass, match

Table chooses match: Daisy
Table: 1 grass: 1 match: 0
Table: 1 grass: 0 match: 0
Table: 0 grass: 0 match: 0
Daisy smokes a cigarette
Table chooses paper: Sandy
Table: 0 grass: 1 match: 1
Table: 0 grass: 1 match: 0
Table: 0 grass: 0 match: 0
Sandy smokes a cigarette
Table chooses match: Daisy
Table: 1 grass: 1 match: 0
Table: 1 grass: 0 match: 0
Table: 0 grass: 0 match: 0
Daisy smokes a cigarette
Go语言教程之边写边学:并发 Concurrency

并发是程序同时执行多项操作的能力。这意味着具有两个或多个任务的程序,这些任务几乎同时单独运行,但仍然是同一程序的一部分。并发性在现代软件中非常重要,因为需要在不干扰程序整体流程的情况下尽可能快地执行独立的代码片段。

Golang中的并发性是函数彼此独立运行的能力。goroutine是一个能够与其他函数同时运行的函数。将函数创建为goroutine时,它被视为一个独立的工作单元,该工作单元在可用的逻辑处理器上调度和执行。Golang运行时调度器具有管理所有创建并需要处理器时间的goroutines的功能。调度程序将操作系统的线程绑定到逻辑处理器,以便执行goroutine。通过位于操作系统之上,调度程序可以控制与在任何给定时间在哪些逻辑处理器上运行哪些goroutines相关的所有内容。

流行的编程语言(如Java和Python)通过使用线程实现并发性。Golang具有内置的并发结构:goroutines和channels。Golang中的并发既便宜又简单。Goroutines是廉价、轻量级的线程。通道是允许 goroutines之间进行通信的管道。

通信顺序进程(简称 CSP)用于描述具有多个并发模型的系统应如何相互交互。它通常严重依赖使用通道作为在两个或多个并发进程之间传递消息的媒介,并且是Golang的基本口头禅。

Goroutines—goroutines是一个独立于启动它的函数运行的函数。
Channel—通道是用于发送和接收数据的管道。通道为一个goroutine提供了一种将结构化数据发送到另一个goroutine的方法。

当您检查多任务处理时,并发性和并行性就会出现,它们通常可以互换使用,并发和并行是指相关但不同的东西。

并发-并发即将同时处理多个任务。这意味着您正在努力管理在给定时间段内同时完成的众多任务。但是,您一次只能执行一项任务。这往往发生在一个任务正在等待并且程序决定在空闲时间内驱动另一个任务的程序中。这是问题域的一个方面-您的程序需要处理许多同时发生的事件。

并行性-并行性是一次执行许多任务。这意味着即使我们有两个任务,它们也会持续工作,它们之间没有任何中断。这是解决方案域的一个方面-您希望通过并行处理问题的不同部分来加快程序。

并发程序具有多个逻辑控制线程。这些线程可能会也可能不会并行运行。通过同时(并行)执行计算的不同部分,并行程序可能比顺序程序运行得更快。它可能具有也可能没有多个逻辑控制线程。

 

哲学家就餐问题

五个沉默的哲学家坐在一张圆桌旁,端着一碗意大利面。叉子放在每对相邻的哲学家之间。每个哲学家都必须交替思考和吃饭。然而,哲学家只有在左右叉子都有时才能吃意大利面。每个叉子只能由一个哲学家握住,因此一个哲学家只有在另一个哲学家不使用叉子的情况下才能使用叉子。在个别哲学家吃完饭后,他们需要放下两把叉子,以便其他人可以使用叉子。哲学家可以在他们可用时拿走他们右边的叉子或左边的叉子,但在得到两个叉子之前不能开始吃东西。进食不受剩余意大利面或胃空间的限制;假设有无限的供应和无限的需求。问题是如何设计一个行为学科(并发算法),这样就不会有哲学家会饿死;也就是说,每个人都可以永远继续在吃饭和思考之间交替,假设没有哲学家知道别人什么时候想吃东西或想思考。

package main

import (
	"hash/fnv"
	"log"
	"math/rand"
	"os"
	"sync"
	"time"
)

// 哲学家数组
var ph = []string{"Mark", "Russell", "Rocky", "Haris", "Root"}

const hunger = 3                // 就餐次数
const think = time.Second / 100 // 思考时间
const eat = time.Second / 100   // 就餐时间

var fmt = log.New(os.Stdout, "", 0)

var dining sync.WaitGroup

func diningProblem(phName string, dominantHand, otherHand *sync.Mutex) {
	fmt.Println(phName, "Seated")
	h := fnv.New64a()
	h.Write([]byte(phName))
	rg := rand.New(rand.NewSource(int64(h.Sum64())))
	rSleep := func(t time.Duration) {
		time.Sleep(t/2 + time.Duration(rg.Int63n(int64(t))))
	}
	for h := hunger; h > 0; h-- {
		fmt.Println(phName, "Hungry")
		dominantHand.Lock() // 就餐
		otherHand.Lock()
		fmt.Println(phName, "Eating")
		rSleep(eat)
		dominantHand.Unlock() // 就餐结束
		otherHand.Unlock()
		fmt.Println(phName, "Thinking")
		rSleep(think)
	}
	fmt.Println(phName, "Satisfied")
	dining.Done()
	fmt.Println(phName, "Left the table")
}

func main() {
	fmt.Println("Table empty")
	dining.Add(5)
	fork0 := &sync.Mutex{}
	forkLeft := fork0
	for i := 1; i < len(ph); i++ {
		forkRight := &sync.Mutex{}
		go diningProblem(ph[i], forkLeft, forkRight)
		forkLeft = forkRight
	}
	go diningProblem(ph[0], fork0, forkLeft)
	dining.Wait() // wait for philosphers to finish
	fmt.Println("Table empty")
}

输出

Table empty
Mark seated
Mark Hungry
Mark Eating
..................
..................
Haris Thinking
Haris Satisfied
Haris Left the table
Table empty

 

检查点同步问题

检查点同步是同步多个任务的问题。考虑一个车间,几个工人组装一些机制的细节。当他们每个人都完成他的工作时,他们会把细节放在一起。没有商店,所以首先完成部分的工人必须等待其他人才能开始另一个部分。将细节放在一起是任务在分开路径之前进行自我同步的检查点。

package main

import (
	"log"
	"math/rand"
	"sync"
	"time"
)

func worker(part string) {
	log.Println(part, "worker begins part")
	time.Sleep(time.Duration(rand.Int63n(1e6)))
	log.Println(part, "worker completes part")
	wg.Done()
}

var (
	partList    = []string{"A", "B", "C", "D"}
	nAssemblies = 3
	wg          sync.WaitGroup
)

func main() {
	rand.Seed(time.Now().UnixNano())
	for c := 1; c <= nAssemblies; c++ {
		log.Println("begin assembly cycle", c)
		wg.Add(len(partList))
		for _, part := range partList {
			go worker(part)
		}
		wg.Wait()
		log.Println("assemble.  cycle", c, "complete")
	}
}

输出

2019/07/15 16:10:32 begin assembly cycle 1
2019/07/15 16:10:32 D worker begins part
2019/07/15 16:10:32 A worker begins part
2019/07/15 16:10:32 B worker begins part
........
2019/07/15 16:10:32 D worker completes part
2019/07/15 16:10:32 C worker completes part
2019/07/15 16:10:32 assemble.  cycle 3 complete

 

生产者消费者问题

该问题描述了两个进程,即生产者和使用者,它们共享一个用作队列的通用固定大小缓冲区。生产者的工作是生成数据,将其放入缓冲区,然后重新开始。同时,使用者正在消耗数据(即,将其从缓冲区中删除),一次一个片段。问题是要确保生成者不会尝试将数据添加到缓冲区中(如果缓冲区已满),并且使用者不会尝试从空缓冲区中删除数据。生产者的解决方案是进入睡眠状态,或者在缓冲区已满时丢弃数据。下次使用者从缓冲区中删除项目时,它会通知生产者,生产者再次开始填充缓冲区。同样,如果使用者发现缓冲区为空,则可以进入睡眠状态。下次生产者将数据放入缓冲区时,它会唤醒沉睡的使用者。

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"runtime/pprof"
)

// 消费者
type Consumer struct {
	msgs *chan int
}

// 新建消费者
func NewConsumer(msgs *chan int) *Consumer {
	return &Consumer{msgs: msgs}
}

// 消费者消费消息
func (c *Consumer) consume() {
	fmt.Println("consume: Started")
	for {
		msg := <-*c.msgs
		fmt.Println("consume: Received:", msg)
	}
}

// 生产者
type Producer struct {
	msgs *chan int
	done *chan bool
}

// 新建生产者
func NewProducer(msgs *chan int, done *chan bool) *Producer {
	return &Producer{msgs: msgs, done: done}
}

// 生产消息
func (p *Producer) produce(max int) {
	fmt.Println("produce: Started")
	for i := 0; i < max; i++ {
		fmt.Println("produce: Sending ", i)
		*p.msgs <- i
	}
	*p.done <- true // signal when done
	fmt.Println("produce: Done")
}

func main() {
	// cpu和内存profile信息保存到文件中
	cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`")
	memprofile := flag.String("memprofile", "", "write memory profile to `file`")

	// get the maximum number of messages from flags
	max := flag.Int("n", 5, "defines the number of messages")

	flag.Parse()

	// 使用所有可用cpu
	runtime.GOMAXPROCS(runtime.NumCPU())

	// CPU Profile
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal("could not create CPU profile: ", err)
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			log.Fatal("could not start CPU profile: ", err)
		}
		defer pprof.StopCPUProfile()
	}

	var msgs = make(chan int)  // 发送和接收消息的管道
	var done = make(chan bool) // 生产者是否完成工作的消息通道

	// 新建生产者并生产消息
	go NewProducer(&msgs, &done).produce(*max)

	// 新建消费者并消费消息
	go NewConsumer(&msgs).consume()

	// 当生产者生产完消息后结束程序
	<-done

	// Memory Profile
	if *memprofile != "" {
		f, err := os.Create(*memprofile)
		if err != nil {
			log.Fatal("could not create memory profile: ", err)
		}
		runtime.GC() // 启动垃圾回收器
		if err := pprof.WriteHeapProfile(f); err != nil {
			log.Fatal("could not write memory profile: ", err)
		}
		f.Close()
	}
}

输出

consume: Started
produce: Started
produce: Sending  0
produce: Sending  1
consume: Received: 0
consume: Received: 1
produce: Sending  2
produce: Sending  3
consume: Received: 2
consume: Received: 3
produce: Sending  4
produce: Done

 

理发师的睡觉问题

理发师在剪裁室里有一把理发椅,还有一个等候室,里面有几把椅子。当理发师剪完顾客的头发后,他解雇了顾客,然后去等候室看看是否有其他人在等。如果有,他把其中一个带回椅子上,剪掉他们的头发。如果没有,他就回到椅子上睡在椅子上。每个顾客到达时,都会看看理发师在做什么。如果理发师正在睡觉,顾客会叫醒他,坐在剪裁室的椅子上。如果理发师正在剪头发,顾客就会留在候诊室。如果候诊室里有免费的椅子,顾客就会坐在里面等待轮到他们。如果没有空闲的椅子,客户就会离开。

package main

import (
	"fmt"
	"sync"
	"time"
)

const (
	sleeping = iota
	checking
	cutting
)

var stateLog = map[int]string{
	0: "Sleeping",
	1: "Checking",
	2: "Cutting",
}
var wg *sync.WaitGroup // 潜在客户数量

type Barber struct {
	name string
	sync.Mutex
	state    int // Sleeping/Checking/Cutting
	customer *Customer
}

type Customer struct {
	name string
}

func (c *Customer) String() string {
	return fmt.Sprintf("%p", c)[7:]
}

func NewBarber() (b *Barber) {
	return &Barber{
		name:  "Sam",
		state: sleeping,
	}
}

// 理发师goroutine
// 检查等候室客户
// 睡觉-等待被叫醒
func barber(b *Barber, wr chan *Customer, wakers chan *Customer) {
	for {
		b.Lock()
		defer b.Unlock()
		b.state = checking
		b.customer = nil

		// 检查等候室
		fmt.Printf("Checking waiting room: %d\n", len(wr))
		time.Sleep(time.Millisecond * 100)
		select {
		case c := <-wr:
			HairCut(c, b)
			b.Unlock()
		default: // 当等候室没有客户时
			fmt.Printf("Sleeping Barber - %s\n", b.customer)
			b.state = sleeping
			b.customer = nil
			b.Unlock()
			c := <-wakers
			b.Lock()
			fmt.Printf("Woken by %s\n", c)
			HairCut(c, b)
			b.Unlock()
		}
	}
}

// 理发
func HairCut(c *Customer, b *Barber) {
	b.state = cutting
	b.customer = c
	b.Unlock()
	fmt.Printf("Cutting  %s hair\n", c)
	time.Sleep(time.Millisecond * 100)
	b.Lock()
	wg.Done()
	b.customer = nil
}

// 客户goroutine
// 如果等候室满了就离开, 否则再大厅等待
func customer(c *Customer, b *Barber, wr chan<- *Customer, wakers chan<- *Customer) {
	// 到达
	time.Sleep(time.Millisecond * 50)
	// 检查理发店
	b.Lock()
	fmt.Printf("Customer %s checks %s barber | room: %d, w %d - customer: %s\n",
		c, stateLog[b.state], len(wr), len(wakers), b.customer)
	switch b.state {
	case sleeping:
		select {
		case wakers <- c:
		default:
			select {
			case wr <- c:
			default:
				wg.Done()
			}
		}
	case cutting:
		select {
		case wr <- c:
		default: // 等候室满了就离开
			wg.Done()
		}
	case checking:
		panic("Customer shouldn't check for the Barber when Barber is Checking the waiting room")
	}
	b.Unlock()
}

func main() {
	b := NewBarber()
	b.name = "Rocky"
	WaitingRoom := make(chan *Customer, 5) // 5把椅子
	Wakers := make(chan *Customer, 1)      // 每次接待一个客户
	go barber(b, WaitingRoom, Wakers)

	time.Sleep(time.Millisecond * 100)
	wg = new(sync.WaitGroup)
	n := 10
	wg.Add(10)
	// 生产客户
	for i := 0; i < n; i++ {
		time.Sleep(time.Millisecond * 50)
		c := new(Customer)
		go customer(c, b, WaitingRoom, Wakers)
	}

	wg.Wait()
	fmt.Println("No more customers for the day")
}

输出

Checking waiting room: 0
Sleeping Barber - 
Customer 120 checks Sleeping barber | room: 0, w 0 - customer: 
Woken by 120
..............
..............
Checking waiting room: 0
No more customers for the day

 

吸烟者问题

假设一支香烟需要三种成分来制作和吸烟:烟草、纸张和火柴。一张桌子周围有三个吸烟者,每个人都有三种成分中的一种无限供应——一个吸烟者有无限的烟草供应,另一个有纸,第三个有火柴。有一个第四者,拥有无限供应的一切,随机选择一个吸烟者,并将香烟所需的另外两种用品放在桌子上,让被选择的吸烟者吸烟,该过程应无限期重复。

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

const (
	paper = iota
	grass
	match
)

var smokeMap = map[int]string{
	paper: "paper",
	grass: "grass",
	match: "match",
}

var names = map[int]string{
	paper: "Sandy",
	grass: "Apple",
	match: "Daisy",
}

type Table struct {
	paper chan int
	grass chan int
	match chan int
}

func arbitrate(t *Table, smokers [3]chan int) {
	for {
		time.Sleep(time.Millisecond * 500)
		next := rand.Intn(3)
		fmt.Printf("Table chooses %s: %s\n", smokeMap[next], names[next])
		switch next {
		case paper:
			t.grass <- 1
			t.match <- 1
		case grass:
			t.paper <- 1
			t.match <- 1
		case match:
			t.grass <- 1
			t.paper <- 1
		}
		for _, smoker := range smokers {
			smoker <- next
		}
		wg.Add(1)
		wg.Wait()
	}
}

func smoker(t *Table, name string, smokes int, signal chan int) {
	var chosen = -1
	for {
		chosen = <-signal // blocks

		if smokes != chosen {
			continue
		}

		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		select {
		case <-t.paper:
		case <-t.grass:
		case <-t.match:
		}
		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		time.Sleep(10 * time.Millisecond)
		select {
		case <-t.paper:
		case <-t.grass:
		case <-t.match:
		}
		fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
		fmt.Printf("%s smokes a cigarette\n", name)
		time.Sleep(time.Millisecond * 500)
		wg.Done()
		time.Sleep(time.Millisecond * 100)
	}
}

const LIMIT = 1

var wg *sync.WaitGroup

func main() {
	wg = new(sync.WaitGroup)
	table := new(Table)
	table.match = make(chan int, LIMIT)
	table.paper = make(chan int, LIMIT)
	table.grass = make(chan int, LIMIT)
	var signals [3]chan int
	// three smokers
	for i := 0; i < 3; i++ {
		signal := make(chan int, 1)
		signals[i] = signal
		go smoker(table, names[i], i, signal)
	}
	fmt.Printf("%s, %s, %s, sit with \n%s, %s, %s\n\n", names[0], names[1], names[2], smokeMap[0], smokeMap[1], smokeMap[2])
	arbitrate(table, signals)
}

输出

Sandy, Apple, Daisy, sit with
paper, grass, match

Table chooses match: Daisy
Table: 1 grass: 1 match: 0
Table: 1 grass: 0 match: 0
Table: 0 grass: 0 match: 0
Daisy smokes a cigarette
Table chooses paper: Sandy
Table: 0 grass: 1 match: 1
Table: 0 grass: 1 match: 0
Table: 0 grass: 0 match: 0
Sandy smokes a cigarette
Table chooses match: Daisy
Table: 1 grass: 1 match: 0
Table: 1 grass: 0 match: 0
Table: 0 grass: 0 match: 0
Daisy smokes a cigarette
  • 当前日期:
  • 北京时间:
  • 时间戳:
  • 今年的第:18周
  • 我的 IP:18.116.118.216
农历
五行
冲煞
彭祖
方位
吉神
凶神
极简任务管理 help
+ 0 0 0
Task Idea Collect