[作业解析]go channel的使用和通过channel实现信号量

channel基础

  1. channel是go语言中的实现go routine之间通讯的一种方式
  2. 由 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)
}

你可能感兴趣的:([作业解析]go channel的使用和通过channel实现信号量)