高朗并发

并发是程序同时执行多项操作的能力。这意味着一个程序具有两个或多个任务,这些任务大约在同一时间彼此独立运行,但仍属于同一程序。并发在现代软件中非常重要,因为需要尽可能快地执行独立的代码段,而又不影响程序的整体流程。

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

流行的编程语言(例如Java和Python)通过使用线程来实现并发。 高朗具有内置的并发构造:goroutine和通道。 高朗中的并发既便宜又容易。 Goroutines是廉价的轻量级线程。通道是允许goroutine之间进行通信的管道。

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

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

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

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

平行性 -并行性是指一次执行很多任务。这意味着即使我们有两个任务,它们仍在继续工作,而它们之间没有任何中断。这是解决方案领域的一个方面,您希望通过并行处理问题的不同部分来加快程序运行速度。

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


高朗的餐饮哲学家问题插图

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

package main

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

// Number of philosophers is simply the length of this list.
var ph = []string{"Mark", "Russell", "Rocky", "Haris", "Root"}

const hunger = 3                // Number of times each philosopher eats
const think = time.Second / 100 // Mean think time
const eat = time.Second / 100   // Mean eat time

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() // pick up forks
		otherHand.Lock()
		fmt.Println(phName, "Eating")
		rSleep(eat)
		dominantHand.Unlock() // put down forks
		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
}

// NewConsumer creates a Consumer
func NewConsumer(msgs *chan int) *Consumer {
	return &Consumer{msgs: msgs}
}

// consume reads the msgs channel
func (c *Consumer) consume() {
	fmt.Println("consume: Started")
	for {
		msg := <-*c.msgs
		fmt.Println("consume: Received:", msg)
	}
}

// Producer definition
type Producer struct {
	msgs *chan int
	done *chan bool
}

// NewProducer creates a Producer
func NewProducer(msgs *chan int, done *chan bool) *Producer {
	return &Producer{msgs: msgs, done: done}
}

// produce creates and sends the message through msgs channel
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() {
	// profile flags
	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()

	// utilize the max num of cores available
	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)  // channel to send messages
	var done = make(chan bool) // channel to control when production is done

	// Start a goroutine for Produce.produce
	go NewProducer(&msgs, &done).produce(*max)

	// Start a goroutine for Consumer.consume
	go NewConsumer(&msgs).consume()

	// Finish the program when the production is done
	<-done

	// Memory Profile
	if *memprofile != "" {
		f, err := os.Create(*memprofile)
		if err != nil {
			log.Fatal("could not create memory profile: ", err)
		}
		runtime.GC() // get up-to-date statistics
		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 // Amount of potentional customers

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,
	}
}

// Barber goroutine
// Checks for customers
// Sleeps - wait for wakers to wake him up
func barber(b *Barber, wr chan *Customer, wakers chan *Customer) {
	for {
		b.Lock()
		defer b.Unlock()
		b.state = checking
		b.customer = nil

		// checking the waiting room
		fmt.Printf("Checking waiting room: %d\n", len(wr))
		time.Sleep(time.Millisecond * 100)
		select {
		case c := <-wr:
			HairCut(c, b)
			b.Unlock()
		default: // Waiting room is empty
			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
}

// customer goroutine
// just fizzles out if it's full, otherwise the customer
// is passed along to the channel handling it's haircut etc
func customer(c *Customer, b *Barber, wr chan<- *Customer, wakers chan<- *Customer) {
	// arrive
	time.Sleep(time.Millisecond * 50)
	// Check on barber
	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: // Full waiting room, leave shop
			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 chairs
	Wakers := make(chan *Customer, 1)      // Only one waker at a time
	go barber(b, WaitingRoom, Wakers)

	time.Sleep(time.Millisecond * 100)
	wg = new(sync.WaitGroup)
	n := 10
	wg.Add(10)
	// Spawn customers
	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 - <nil>
Customer 120 checks Sleeping barber | room: 0, w 0 - customer: <nil>
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