Fragment Description:



Defining a Semaphore object using the channel artefact and its characteristics.
(See example using the go sync.Cond type in 'Concurrency/syncCondAsSemaphore'.
Semaphores are one of the primitives used to implement synchronization of threads (with mutexes and monitors) sharing resources.
A semaphore is a variable or abstract data type that is used for controlling access, by multiple processes, to a common resource in a concurrent system.
The value of the semaphore is the number of units of the resource that are currently available.
-P:
The P operation wastes time or sleeps until a resource protected by the semaphore becomes available, at which time the resource is immediately claimed.
P must block if there are no available resources.
- V:
The V operation is the inverse:
it makes a resource available again after the process has finished using it.
V must unblock processes waiting on the semaphore.
One important property of a semaphore is that its value cannot be changed except by using the V and P operations.
Semaphore are simple and versatile, but because they are low level primitives they can be error prone.
To be noted, we have in the sync package:
1- 'sync.Waitgroup' type implementing the following methods:
sync.WaitGroup.Add(int), sync.WaitGroup.Done(), sync.WaitGroup.Wait().
2- 'syncCond' type implementing the following methods:
func (c *Cond) Broadcast(), func (c *Cond) Signal(), func (c *Cond) Wait().
There are not semaphores...
but can be useful to implement them.
The comment below, from Marcelo Magellon, was pertinent (I keep it for the record) and was aiming at a previous version of the fragment:
'channelAsSemaphore'.
Originally it had been written for fun and quickly, too quickly.
The comment from Marcello has made me re-write this example in a more pedagogic and (I hope) correct way.
Thanks, Marcelo.
'gofragments.net', will soon host other examples of Semaphores use, written by Marcelo.
Marcelo has started writing solutions in Go to the puzzles introduced in The Little Book of Semaphores by Allen B.
Downey.
Concurrency has been a key concept in the design of Go.
Concurrency is a vast and difficult subject, but extremely decisive to computer science.
Go simplifies massively its implementation.
We will keep on adding fragments about this.


channelAsSemaphore

Go Playground

Last update, on 2015, Thu 15 Oct, 19:17:42

/* ... <== see fragment description ... */

package main

import (
    "fmt"
    "sync"
)

// By convention (Edsger Dijkstra, 1963) semaphores have 2 operations: P and
// V
type Semaphore interface {
    P() // by convention named P for passing|decrease|Wait|procure
    V() // by convention named V for release|increase|Signal|vacate
}

// SemChan is a channel-based 'semaphore' implementation
// it satisfies the Semaphore interface
//
type SemChan chan struct{}

func NewSemaphore() Semaphore {
    // here, the Semaphore is a buffered channel
    NewSepmaphore := make(SemChan, 1)
    NewSepmaphore.V() // initialized to 0 (i.e.: locked or resource not available)
    return NewSepmaphore
}

// passing-decrease-Wait-procure, a datum is read out of the buffered channel
// receiver, should block if no emission appears on the channel s
//
func (s SemChan) P() {
    <-s
}

// release-increase-Signal-vacate, a datum is send onto the buffered channel
// sender, should unblock any receiver listening on the channel s
//
func (s SemChan) V() {
    s <- struct{}{} // an empty struct, no allocation, just a signal
}
func main() {
    var (
        x, y           int
        wgBegin, wgEnd sync.WaitGroup
    )
    semaphore := NewSemaphore()

    // we are using sync.WaitGroup to organize a contrived control flow
    //
    wgBegin.Add(1)
    wgEnd.Add(1)

    // P() indicates that the resource x is locked, and blocks here
    // until a Signal from V() to release x is received (see  second V() below).
    // the goroutine cannot use 'x' before it is released (|semaphore increased)
    //
    semaphore.P()

    // we start a concurrent process that should update 'y', but only
    // after 'x' itself has been updated
    //
    go func() {
        // guess what occurs for this statement?
        x = 12

        // wgBegin.Done() signals that statement 'x = 10' and following
        // can be executed
        wgBegin.Done()

        // P() indicates that we lock the resource y!
        semaphore.P()
        y = x * 2

        // here V() releases/unlock the resource y,
        // try to comment the following line and the above line with P()
        semaphore.V()
        wgEnd.Done()

    }()

    // from here we wait a wgBegin.Done() signal
    //
    wgBegin.Wait()
    x = 10

    // the following statement should not succeed, the goroutine locks y
    // until wgEnd.Done() is received, not yet here
    //
    y = 1500

    // here V() releases the resource x, the goroutine receives a signal
    //
    semaphore.V()
    wgEnd.Wait()

    // let's check if we have the expected outputs
    //
    if y == 1500 {
        fmt.Printf("The semaphore operation P() failed to lock y;\nExpecting 10 and 20 respectively, we get x = %d, y = %d\n", x, y)
    } else if !(x == 10 && y == 20) {
        fmt.Printf("The semaphore operation P() failed to lock x,y;\nExpecting 10 and 20 respectively, we get x = %d, y = %d\n", x, y)
    } else {
        fmt.Printf("The semaphores operations P() succeeded to lock both 'x' and 'y';\nas expected we get x = %d, y = %d\n", x, y)
    }
}



Comments