source: project/wiki/eggref/5/gochan @ 37625

Last change on this file since 37625 was 37625, checked in by wasamasa, 6 months ago

Fix link

File size: 8.7 KB
Line 
1== chicken-gochan
2[[toc:]]
3[[http://golang.org/|Go]]-inspired channels for [[http://call-cc.org/|Chicken Scheme]] ([[http://synthcode.com/wiki/chibi-scheme|Chibi Scheme]] might work
4too). Essentially thread-safe fifo queues that are useful for thread
5communication and synchronization.
6
7
8=== Dependencies
9* [[matchable]]
10
11
12=== Development Status
13Currently supported:
14
15* receive and send switch ({{gochan-select}})
16* buffered channels
17* timeouts as ordinary receive on a channel
18* closable channels with close-reason (aka {{fail-flag}})
19* load-balancing when multiple channels have data ready
20
21Source code can be found [[https://github.com/Adellica/chicken-gochan|here]].
22
23
24=== Comparison to real Go Channels
25The API and behaviour largely follows [[http://golang.org/|Go]]'s channel API, with some
26exceptions:
27
28* channels don't have any type information
29* sending to a channel that gets closed does not panic, it unblocks
30  all senders immediately with the {{fail}} flag set to non-{{#f}}.
31* closing an already closed channel does not result in error.
32* {{nil}}-channels aren't supported, create new forever-blocking {{(gochan 0)}} instead.
33* Unlike in [[http://golang.org/|Go]], you can choose what channels to select on at runtime with {{gochan-select*}}
34
35
36=== Comparison to [[https://github.com/clojure/core.async|core.async]]
37Honestly, I wish I had stolen the [[https://github.com/clojure/core.async|core.async]] API instead of the [[http://golang.org/|Go]] channel API
38since that's already a LISP, but here is what we have for now:
39
40* {{alt!}} is {{gochan-select}}
41* {{alts!}} is {{gochan-select*}}
42* {{<!}} is {{gochan-recv}}
43* {{>!}} is {{gochan-send}}
44* There is no distinction between {{park}} and {{block}} because CHICKEN
45  doesn't have native threads.
46* The operations don't need to be called inside {{(go ...)}} blocks.
47
48
49=== API
50<procedure> (gochan capacity)</procedure>
51
52Construct a channel with a maximum buffer-size of {{capacity}}. If
53{{capacity}} is {{0}}, the channel is unbuffered and all its operations
54will block until a remote end sends/receives.
55
56<syntax> (gochan-select ((chan <-|-> msg [ fail ]) body ...) ... [(else body ...])</syntax>
57
58This is a channel switch that will send or receive on a single
59channel, picking whichever clause is able to complete soonest. If no
60clause is ready, {{gochan-select}} will block until one does, unless
61{{else}} is specified which will by execute its body instead of
62blocking. Multiple send and receive clauses can be specified
63interchangeably. Note that only one clause will be served.
64
65Here's an example:
66
67<enscript highlight="scheme">    
68(gochan-select
69  ((chan1 -> msg fail) (if fail (print "chan1 closed!") (print "chan1 says " msg)))
70  ((chan2 -> msg fail) (if fail (print "chan2 closed!") (print "chan2 says " msg))))
71</enscript>
72
73Receive clauses, {{((chan -> msg [fail]) body ...)}}, execute {{body}}
74with {{msg}} bound to the message object and {{fail}} bound to a flag
75indicating failure. Receiving from a closed channel immediately
76completes with this {{fail}} flag set to non-{{#f}}.
77
78Send clauses, {{((chan <- msg [fail]) body ...)}}, execute {{body}} after
79{{msg}} has been sent to a receiver, successfully buffered onto the
80channel, or if channel was closed. Sending to a closed channel
81immediately completes with the {{fail}} flag set to {{#f}}.
82
83A send or receive clause on a closed channel with no {{fail}}-flag
84binding specified will immediately return {{(void)}} without executing
85{{body}}. This can be combined with recursion like this:
86
87<enscript highlight="scheme">    
88;; loop forever until either chan1 or chan2 closes
89(let loop ()
90   (gochan-select
91    ((chan1 -> msg) (print "chan1 says " msg) (loop))
92    ((chan2 <- 123) (print "chan2 got  " 123) (loop))))
93</enscript>
94
95Or like this:
96
97<enscript highlight="scheme">    
98;; loop forever until chan1 closes. replacing chan2 is important to avoid busy-wait!
99(let loop ((chan2 chan2))
100  (gochan-select
101   ((chan1 -> msg)      (print "chan1 says " msg) (loop chan2))
102   ((chan2 -> msg fail) (if fail
103                            (begin (print "chan2 closed, keep going")
104                                   ;; create new forever-blocking channel
105                                   (loop (gochan 0)))
106                            (begin (print "chan2 says " msg)
107                                   (loop chan2))))))
108</enscript>
109
110{{gochan-select}} returns the return-value of the executed clause's
111body.
112
113To do a non-blocking receive, you can do the following:
114
115<enscript highlight="scheme">    
116(gochan-select ((chan1 -> msg fail) (if fail #!eof msg))
117               (else 'eagain))
118
119</enscript>
120
121<procedure> (gochan-select* chans)</procedure>
122
123This procedure allows selecting channels that are chosen
124programmatically.  It takes input that looks like this:
125
126<enscript highlight="scheme">
127(gochan-select `((,chan1 meta1)
128                 (,chan2 meta2 message)
129                 (,chan3 meta3) ...))
130</enscript>
131
132It returns three values, {{msg fail meta}},
133where {{msg}} is the message that was sent over the channel, {{fail}} is true
134if the channel was closed and false otherwise, and {{meta}} is the datum supplied
135in the arguments.
136
137For example, if a message arrived on {{chan3}} above, for example, {{meta}} would be
138{{'meta3}} in that case. This allows you to see which channel a message
139came from (i.e. if you supply meta data that is the channel itself)
140
141<procedure> (gochan-send chan msg)</procedure>
142
143
144This is short for {{(gochan-select ((chan <- msg fail) (values  #f fail #t)))}}.
145
146<procedure> (gochan-recv chan)</procedure>
147
148This is short for {{(gochan-select ((chan -> msg fail) (values msg fail #t)))}}.
149
150<procedure> (gochan-close chan [fail-flag])</procedure>
151
152Close the channel. Note that this will unblock existing receivers and
153senders waiting for an operation on {{chan}} with the {{fail-flag}} set to
154a non-{{#f}} value. All ''future'' receivers and senders will also
155immdiately unblock in this way, so watch out for busy-loops.
156
157The optional {{fail-flag}} can be used to specify an alternative to the
158default {{#t}}. As this value is given to all receivers and senders of
159{{chan}}, the {{fail-flag}} can be used as a "broadcast"
160mechanism. {{fail-flag}} cannot be {{#f}} as that would indicate a
161successful message transaction.
162
163Closing an already closed channel will results in its {{fail-flag}}
164being updated.
165
166<procedure> (gochan-after duration/ms)</procedure>
167
168Returns a {{gochan}} that will "send" a single message after
169{{duration/ms}} milliseconds of its creation. The message is the
170{{(current-milliseconds)}} value at the time of the timeout (not when
171the message was received). Receiving more than once on an
172{{gochan-after}} channel will block indefinitely or deadlock the second
173time.
174
175<enscript highlight="scheme">    
176(gochan-select
177 ((chan1 -> msg)                (print "chan1 says " msg))
178 (((gochan-after 1000) -> when) (print "chan1 took too long")))
179</enscript>
180
181You cannot send to or close a timer channel. These are special records
182that contain information about when the next timer will
183trigger. Creating timers is a relatively cheap operation, and
184unlike [[https://golang.org/pkg/time/#After|golang.time.After]], may be
185garbage-collected before the timer triggers. Creating a timer does not
186spawn a new thread.
187
188<procedure> (gochan-tick duration/ms)</procedure>
189
190Return a {{gochan}} that will "send" a message every {{duration/ms}}
191milliseconds. The message is the {{(current-milliseconds)}}
192value at the time of the tick (not when it was received).
193
194See [[tests/worker-pool.scm|{{tests/worker-pool.scm}}]] for
195an example of its use.
196
197<syntax> (go body ...)</syntax>
198
199Starts and returns a new srfi-18 thread. Short for {{(thread-start!
200(lambda () body ...))}}.
201
202
203=== Samples
204* See [[https://github.com/Adellica/chicken-gochan/blob/master/tests/worker-pool.scm|{{tests/worker-pool.scm}}]] for a port of
205  [[https://gobyexample.com/worker-pools|this Go example]].
206* See [[https://github.com/Adellica/chicken-gochan/blob/master/tests/secret.scm|{{tests/secret.scm}}]] for a port of
207  [[https://blog.jayway.com/2014/09/16/comparing-core-async-and-rx-by-example/|this]]
208  [[https://github.com/clojure/core.async|core.async]]/[[https://github.com/Reactive-Extensions/RxJS|rxjs]] challenge.
209
210
211=== TODO
212* Perhaps rename the API to [[https://github.com/clojure/core.async|core.async]]'s?
213* Add {{go-map}}, {{go-fold}} and friends (hopefully simple because we can also do [[http://clojure.github.io/core.async/#clojure.core.async/map|this]])
214* Support customizing buffering behaviour, like [[https://github.com/clojure/core.async|core.async]]'s [[http://clojure.github.io/core.async/#clojure.core.async/dropping-buffer|{{dropping-buffer}}]] and [[http://clojure.github.io/core.async/#clojure.core.async/sliding-buffer|{{sliding-buffer}}]] (harder!)
215* Add a priority option to {{gochan-select*}}?
216* Support cancelling timers
217
Note: See TracBrowser for help on using the repository browser.