/
protect.go
97 lines (89 loc) · 2.46 KB
/
protect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package parallel
import (
"sync"
)
// Protect offers safe concurrent access to a protected value.
// It is a "share memory by communicating" alternative
// to protecting the value with [sync.RWMutex].
//
// The caller gets back three functions:
// a reader for getting the protected value,
// a writer for updating it,
// and a closer for releasing resources
// when no further reads or writes are needed.
//
// Any number of calls to the reader may run concurrently.
// If T is a "reference type" (see below)
// then the caller should not make any changes
// to the value it receives from the reader.
//
// A call to the writer prevents other reader and writer calls from running until it is done.
// It waits for pending calls to finish before it executes.
// After a call to the writer,
// future reader calls will receive the updated value.
//
// The closer should be called to release resources
// when no more reader or writer calls are needed.
// Calling any of the functions (reader, writer, or closer)
// after a call to the closer may cause a panic.
//
// The term "reference type" here means a type
// (such as pointer, slice, map, channel, function, and interface)
// that allows a caller C
// to make changes that will be visible to other callers
// outside of C's scope.
// In other words,
// if the type is int and caller A does this:
//
// val := reader()
// val++
//
// it will not affect the value that caller B sees when it does its own call to reader().
// But if the type is *int and caller A does this:
//
// val := reader()
// *val++
//
// then the change in the pointed-to value _will_ be seen by caller B.
//
// For more on the fuzzy concept of "reference types" in Go,
// see https://github.com/go101/go101/wiki/About-the-terminology-%22reference-type%22-in-Go
func Protect[T any](val T) (reader func() T, writer func(T), closer func()) {
ch := make(chan rwRequest[T])
go func() {
var wg sync.WaitGroup
for req := range ch {
req := req // Go loop var pitfall
if req.r != nil {
wg.Add(1)
go func() {
req.r <- val
close(req.r)
wg.Done()
}()
continue
}
wg.Wait()
val = <-req.w
}
}()
reader = func() T {
valch := make(chan T, 1)
ch <- rwRequest[T]{r: valch}
return <-valch
}
writer = func(val T) {
valch := make(chan T, 1)
valch <- val
ch <- rwRequest[T]{w: valch}
close(valch)
}
closer = func() {
close(ch)
}
return reader, writer, closer
}
type rwRequest[T any] struct {
r chan<- T
w <-chan T
}