channel基础
- channel是go语言中的实现go routine之间通讯的一种方式
- 由 channel支持同步和异步通讯,同步chan由
make (chan type)
创建,类似于生产者和消费者,一方缺失时,另一方就会被阻塞;而异步则通过make (chan type,N)
, 表示该channel自带N个type类型大小的buffer,只有该chan满/空时,调用方才会被阻塞
4.对于同步的channel,必须先有接收者<-chan
在前,然后才能有发送者发送,不然会产生异常
题1:通过channel实现信号量
先复习一下信号量的基础,信号量(Semaphore)是个非负整数,表示可用的资源数。
信号量有两个操作signal和wait,当一个线程Q采用wait()操作时,表示该线程想获取信号量并且进入critical section,如果信号量V此时大于0,表示可以被获取,该线程Q.state = ready准备进入CS,并且信号量V-1;否则线程Q被阻塞q.state = blocked,并且被放入等待集合M中。
当一个线程从critcal section出来时,会调用signal()操作,表示释放信号量。此调度器Schedule会检查等待集合M是否为空, 是的话,此时信号量V+1;否则激活一个在等待队列中的线程Q,Q.state = ready, Q准备进入critical section.
信号量我通过newSem
实现,它有个参数semChan,提供了两个通道,request和reply,前者用于想进入/离开critical section的worker线程发送请求,后者用于semaphore对线程的回应,表示允许该线程进入critical section
所有的worker线程都共用这两个通道,semaphore用v来表示,当为v0时,由blocked来记录等待中的线程,也就是那些因为 <-reply
而堵塞的线程,他们能收到东西了,就能运行了。
代码中产生随机数的地方没有直接用rand.Intn()
因为这个方法内部貌似自带一个互斥锁,强制线性生成?
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"time"
)
type semChan struct {
request chan string
reply chan bool
}
func wait(sem semChan) {
sem.request <- "wait"
<-sem.reply
}
func signal(sem semChan) {
sem.request <- "signal"
}
var workerInCS = 0
func worker(semCh semChan, i int, mutexLock chan bool) {
source := rand.NewSource(time.Now().UnixNano())
generator := rand.New(source)
for {
wait(semCh)
<-mutexLock
workerInCS += 1
mutexLock <- true
duration := generator.Intn(100)
fmt.Println("worker" + strconv.Itoa(i) + " start working for " + strconv.Itoa(duration) + "microseconds")
fmt.Println(strconv.Itoa(workerInCS) + " workers in C.S.")
time.Sleep(time.Duration(duration) * time.Microsecond)
<-mutexLock
workerInCS -= 1
mutexLock <- true
signal(semCh)
}
}
func newSem(sem semChan, v int) {
blocked := 0
for {
x := <-sem.request
if x == "wait" {
if v == 0 {
blocked += 1
} else {
sem.reply <- true
v -= 1
}
}
if x == "signal" {
if blocked > 0 {
sem.reply <- true
blocked -= 1
} else {
v += 1
}
}
}
}
func main() {
v := 5
request := make(chan string)
reply := make(chan bool)
semCh := semChan{request, reply}
mutexLock := make(chan bool)
go newSem(semCh, v)
for i := 0; i < 10; i++ {
go worker(semCh, i, mutexLock)
}
mutexLock <- true
reader := bufio.NewReader(os.Stdin) // read the output of go routine
reader.ReadString('\n')
}
题2:异步channel采用蒙特卡洛模拟计算Pi的值
package main
import (
"fmt"
"math"
"math/rand"
"time"
)
func simulation(c chan bool, id int, numPoints int) {
fmt.Printf("[Routine %d] Starte Monte-Carlo-Simulation auf %d Punkten\n",
id, numPoints)
// Generator fuer Zufallszahlen erzeugen
source := rand.NewSource(time.Now().UnixNano())
generator := rand.New(source)
// Zufallszahlen koennen nun mit
generator.Float64()
// erzeugt werden...
//
// Erzeuge numPoints viele zufaellige zweidimensionale Punkte und prüfe
// für jeden Punkt, ob er im Einheitskreis liegt.
// Sende anschließend das Ergebnis über Kanal c an den Main-Thread
// TODO
for i := 0; i < numPoints; i++ {
if (math.Pow(generator.Float64(), 2) + math.Pow(generator.Float64(), 2)) <= 1 {
c <- true
}
}
close(c)
fmt.Printf("[Routine %d] Simulation beendet\n", id)
}
func main() {
// Definiere Variablen fuer die Anzahl N aller Punkte im Quadrat.
// Definiere eine Variable fuer die Anzahl aller Go-Routinen, die
// die Simulation durchfuehren sollen.
// Erzeuge einen Kanal fuer die Ergebnisse.
// Teile Anzahl der Punkte moeglich gerecht auf alle Routinen auf und
// fuehre fuer jede Routine die Simulation durch.
// Lese die Ergebnisse vom Kanal
// Ermittle die Näherung von pi und vergleiche mit der Konstanten math.Pi.
// TODO
numChannels := 100
N := 1000000
sum := 0
chans := make([](chan bool), numChannels)
for i := range chans {
chans[i] = make(chan bool, 100)
}
for i := 0; i < numChannels; i++ {
if i < numChannels-1 {
go simulation(chans[i], i, N/numChannels)
} else {
go simulation(chans[i], i, N/numChannels+N%numChannels)
}
}
i := 0
closed := 0
for closed < numChannels {
if chans[i%numChannels] != nil {
_, more := <-chans[i%numChannels]
if more {
sum += 1
} else {
chans[i%numChannels] = nil
closed += 1
}
}
i += 1
}
fmt.Println(sum)
pi_ := float64(sum) * 4.0 / float64(N)
fmt.Println("Wert fuer Pi durch Monte Carlo simulation", pi_)
fmt.Println("Wert fuer Pi in Go:", math.Pi)
fmt.Println("Differenz:", pi_-math.Pi)
}
题3:channel解哲学家问题,用上select 和case
package main
// Deadlock-freie Version: Letzter Philosoph nimmt die Gabeln in umgekehrter Reihenfolge
/*
Pseudocode:
for each forks i, it has 2 reference, forks[i] is the left reference and forks[(i+n-1) % 2n] is the right reference
for each Philosophia i, he has 2 forks, fork[i], forks[i+10]
#Philosophia p0 p1 ...
left fork: f0 f1 ...
right fork f10 f11 ...
note that the f1 == f10
loop forever
select {
either
forks[i] <- True
or
forks[i+9] <- True
}
select{
either
<-forks[i]
or
<-forks[i+9]
}
end loop
Philosoph()
loop forever
if i not last
<-fork[i]
<-fork[i+10]
essen()
fork[i]<- T
fork[i+10]<- T
end if
else
<-fork[i+10]
<-fork[i]
essen()
fork[i+10] <- T
fork[i] <- T
end else
end loop
*/
import (
"bufio"
"fmt"
"os"
"strconv"
)
func fork(forks [](chan bool), i int) {
for {
select {
case forks[i] <- true:
case forks[(i+9)%20] <- true: // i the left fork == (i+9) the right fork
}
select {
case <-forks[i]:
case <-forks[(i+9)%20]:
}
}
}
func philosopher(forks [](chan bool), i int) {
// for the same philosopher, i the left , i+10 the right
for {
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Denke...")
if i == 9 {
<-forks[i+10]
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Habe rechte Gabel")
} else {
<-forks[i]
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Habe linke Gabel")
}
if i == 9 {
<-forks[i]
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Habe linke Gabel")
} else {
<-forks[i+10]
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Habe rechte Gabel")
}
fmt.Println("Philosoph: " + strconv.Itoa(i) + ": Esse...")
forks[i] <- true
forks[i+10] <- true
}
}
func main() {
n := 10
//Gabel erstellen
forks := make([](chan bool), 2*n)
for i := range forks {
forks[i] = make(chan bool)
}
for i := 0; i < n; i++ {
go fork(forks, i)
}
for i := 0; i < n; i++ {
go philosopher(forks, i)
}
reader := bufio.NewReader(os.Stdin) // read the output of go routine
reader.ReadString('\n')
// time.Sleep(3 * time.Second)
}