This commit is contained in:
kayos@tcp.direct 2021-09-24 18:47:39 -07:00
commit 659ae65a59
3 changed files with 371 additions and 0 deletions

80
atomic/atomic.go Normal file
View File

@ -0,0 +1,80 @@
package main
import (
"math/rand"
"sync/atomic"
"time"
)
type counterID uint32
const (
finishedCt counterID = iota // 0
workingCt // 1
jobsCt // 2
lastNumber // 3
)
const (
stateUnlocked uint32 = iota
stateLocked
)
var (
setLocker = stateUnlocked
incLocker = stateUnlocked
decLocker = stateUnlocked
heatLocker = stateUnlocked
)
type counter struct {
value atomic.Value
}
var counters map[counterID]*counter
func randSleep() {
rand.Seed(time.Now().UnixNano())
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
}
func init() {
// instantiate our map
counters = make(map[counterID]*counter)
// instantiate value pointer to each of our counters, referenced by it's respective counterID via our map
for ctr := uint32(0); ctr < 4; ctr++ {
counters[counterID(ctr)] = &counter{value: atomic.Value{}}
// store 0 in each counter otherwise we will return nil when trying to do math and panic
counters[counterID(ctr)].value.Store(0)
}
}
func get(ctr counterID) int {
return counters[ctr].value.Load().(int)
}
func set(ctr counterID, val int) {
for !atomic.CompareAndSwapUint32(&setLocker, stateUnlocked, stateLocked) {
randSleep()
}
defer atomic.StoreUint32(&setLocker, stateUnlocked)
counters[ctr].value.Store(val)
}
func inc(ctr counterID) {
for !atomic.CompareAndSwapUint32(&incLocker, stateUnlocked, stateLocked) {
randSleep()
}
defer atomic.StoreUint32(&incLocker, stateUnlocked)
counters[ctr].value.Store(get(ctr) + 1)
}
func dec(ctr counterID) {
for !atomic.CompareAndSwapUint32(&decLocker, stateUnlocked, stateLocked) {
randSleep()
}
defer atomic.StoreUint32(&decLocker, stateUnlocked)
counters[ctr].value.Store(get(ctr) - 1)
}

145
atomic/main.go Normal file
View File

@ -0,0 +1,145 @@
package main
import (
"fmt"
"os"
"strconv"
"sync/atomic"
"time"
)
var (
jobChoice string
maxWorkers = 2
)
type obj struct {
//
}
var (
finish chan struct{}
done chan struct{}
jobChan chan struct{}
start chan struct{}
)
func init() {
var err error
usage := func() {
println("syntax: " + os.Args[0] + " <job1|job2> <workers>")
os.Exit(0)
}
if len(os.Args) < 2 {
usage()
}
if os.Args[1] != "job1" && os.Args[1] != "job2" {
usage()
}
jobChoice = os.Args[1]
maxWorkers, err = strconv.Atoi(os.Args[2])
if err != nil {
usage()
}
finish = make(chan struct{})
done = make(chan struct{})
jobChan = make(chan struct{})
start = make(chan struct{})
seen = make(map[int]bool)
}
func checkHeat() {
if !atomic.CompareAndSwapUint32(&heatLocker, stateUnlocked, stateLocked) {
return
}
defer atomic.StoreUint32(&heatLocker, stateUnlocked)
// if working is zero, and finished is greater (hopefully not) or more likely equal to jobs, we are done
if get(workingCt) == 0 && get(finishedCt) >= get(jobsCt) {
// this will pop out at the bottom of main, allowing execution to complete and the program to exit
finish <- obj{}
}
}
func bigHomie() {
<-start
for {
select {
// job finished
case <-done:
// decrement working count
dec(workingCt)
// increase finished count
inc(finishedCt)
case <-jobChan:
go work()
default:
// check if we're done
checkHeat()
}
}
}
func work() {
for {
// we're at maximum capacity, don't start any new jobs
if get(workingCt) >= maxWorkers {
time.Sleep(100 * time.Millisecond)
continue
}
// room for more workers available, continue execution
break
}
// increase working count
inc(workingCt)
switch jobChoice {
case "job1":
go job1(get(jobsCt) - get(finishedCt))
case "job2":
go job2()
default:
panic("uhh? that job doesn't exist")
}
}
var seen map[int]bool
var seenLock = stateUnlocked
func job1(input int) {
var ok bool
for !atomic.CompareAndSwapUint32(&seenLock, stateUnlocked, stateLocked) {
randSleep()
}
if _, ok = seen[input]; !ok {
seen[input] = true
print(input)
print(" ")
}
atomic.StoreUint32(&seenLock, stateUnlocked)
// sleep just for visual effect
time.Sleep(1 * time.Second)
done <- obj{}
}
func job2() {
fmt.Println(time.Now())
done <- obj{}
}
func main() {
go bigHomie()
for n := 0; n < 100; n++ {
inc(jobsCt)
go func() {
jobChan <- obj{}
}()
}
println("\nrunning " + os.Args[1] + " with " + os.Args[2] + " worker(s)\n")
if os.Args[1] == "job1" {
print("remaining jobs: ")
}
start <- obj{}
<-finish
print("\n\ndone")
}

146
normal/main.go Normal file
View File

@ -0,0 +1,146 @@
package main
import (
"fmt"
"os"
"strconv"
"sync"
"time"
)
var (
jobChoice string
maxWorkers = 2
finished int
working int
jobs int
l *sync.RWMutex
)
type obj struct {
//
}
var (
finish chan struct{}
done chan struct{}
jobChan chan struct{}
start chan struct{}
)
func init() {
var err error
usage := func() {
println("syntax: " + os.Args[0] + " <job1|job2> <workers>")
os.Exit(0)
}
if len(os.Args) < 2 {
usage()
}
if os.Args[1] != "job1" && os.Args[1] != "job2" {
usage()
}
jobChoice = os.Args[1]
maxWorkers, err = strconv.Atoi(os.Args[2])
if err != nil {
usage()
}
finish = make(chan struct{})
done = make(chan struct{})
jobChan = make(chan struct{})
start = make(chan struct{})
l = &sync.RWMutex{}
}
func checkHeat() {
l.RLock()
defer l.RUnlock()
if working == 0 && finished >= jobs {
finish <- obj{}
}
}
func bigHomie() {
<-start
for {
select {
case <-done:
l.Lock()
working--
finished++
l.Unlock()
case <-jobChan:
go work()
default:
checkHeat()
}
}
}
func work() {
for {
l.RLock()
if working > maxWorkers {
l.RUnlock()
time.Sleep(100 * time.Millisecond)
continue
}
l.RUnlock()
break
}
l.Lock()
working++
switch jobChoice {
case "job1":
go job1(jobs - finished)
case "job2":
go job2()
}
l.Unlock()
}
func job1(input int) {
l.Lock()
if input != lastcount {
print(input)
print(" ")
}
lastcount = input
l.Unlock()
// sleep just for visual effect
time.Sleep(1 * time.Second)
done <- obj{}
}
func job2() {
fmt.Println(time.Now())
// sleep just for visual effect
done <- obj{}
}
func main() {
go bigHomie()
for n := 0; n < 100; n++ {
jobs++
go func() {
jobChan <- obj{}
}()
}
println("\nrunning " + os.Args[1] + " with " + os.Args[2] + " worker(s)\n")
if os.Args[1] == "job1" {
print("remaining jobs: ")
}
start <- obj{}
<-finish
print("\n\ndone")
}