core/sync/chan
sync_chan
Types
4Chan
Chan :: struct {}SourceA typed wrapper around Raw_Chan which should be used preferably.
Note: all procedures accepting Raw_Chan also accept Chan.
Inputs
Example:
import "core:sync/chan"
chan_example :: proc() {
// Create an unbuffered channel with messages of type int,
// supporting both sending and receiving.
// Creating unidirectional channels, although possible, is useless.
c, _ := chan.create(chan.Chan(int), context.allocator)
defer chan.destroy(c)
// This channel can now only be used for receiving messages
recv_only_channel: chan.Chan(int, .Recv) = chan.as_recv(c)
// This channel can now only be used for sending messages
send_only_channel: chan.Chan(int, .Send) = chan.as_send(c)
}Direction
Direction :: enum int {
Send = -1,
Both = 0,
Recv = 1,
}SourceDetermines what operations Chan supports.
Raw_Chan
Raw_Chan :: struct {
// Shared
allocator: runtime.Allocator,
allocation_size: int,
msg_size: u16,
closed: b16,
mutex: sync.Mutex,
r_cond: sync.Cond,
w_cond: sync.Cond,
r_waiting: int,
w_waiting: int,
did_read: bool,
// Buffered
queue: ^Raw_Queue,
// Unbuffered
unbuffered_data: rawptr,
}SourceRaw_Chan allows for thread-safe communication using fixed-size messages. This is the low-level implementation of Chan, which does not include the concept of Direction.
Example:
import "core:sync/chan"
raw_chan_example :: proc() {
// Create an unbuffered channel with messages of type int,
c, _ := chan.create_raw(size_of(int), align_of(int), context.allocator)
defer chan.destroy(c)
}Select_Status
Select_Status :: enum int {
None = 0,
Recv = 1,
Send = 2,
}SourceSpecifies the direction of the selected channel.
Procedures
25as_recv
as_recv :: proc(c: C) -> (r: Chan(T, .Recv))SourceCreates a version of a channel that can only be used for receiving not sending.
Inputs
c: The channel
Returns:
- An
Allocator_Error
Example:
import "core:sync/chan"
as_recv_example :: proc() {
consumer :: proc(c: chan.Chan(int, .Recv)) {
value, ok := chan.recv(c)
// compile-time error:
// chan.send(c, 22)
}
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
chan.send(c, 112)
consumer(chan.as_recv(c))
}as_send
as_send :: proc(c: C) -> (s: Chan(T, .Send))SourceCreates a version of a channel that can only be used for sending not receiving.
Inputs
c: The channel
Returns:
- An
Allocator_Error
Example:
import "core:sync/chan"
as_send_example :: proc() {
// this procedure takes a channel that can only
// be used for sending not receiving.
producer :: proc(c: chan.Chan(int, .Send)) {
chan.send(c, 112)
// compile-time error:
// value, ok := chan.recv(c)
}
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
producer(chan.as_send(c))
}can_recv
can_recv :: proc(c: ^Raw_Chan) -> (bool)SourceReturns whether a message can be read without blocking the current thread. Specifically, it checks if the channel is buffered and not full, or if there is already a writer attempting to send a message.
Inputs
c: The channel
Returns
trueif a message can be read,falseotherwise
Example:
import "core:sync/chan"
can_recv_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
assert(!chan.can_recv(c), "the cannel is empty")
assert(chan.send(c, 2))
assert(chan.can_recv(c), "there is message to read")
}can_send
can_send :: proc(c: ^Raw_Chan) -> (bool)SourceReturns whether a message can be sent without blocking the current thread. Specifically, it checks if the channel is buffered and not full, or if there is already a reader waiting for a message.
Inputs
c: The channel
Returns
trueif a message can be sent,falseotherwise
Example:
import "core:sync/chan"
can_send_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
assert(chan.can_send(c), "the channel's buffer is not full")
assert(chan.send(c, 2))
assert(!chan.can_send(c), "the channel's buffer is full")
}cap
cap :: proc(c: ^Raw_Chan) -> (int)SourceReturns the number of elements the channel could hold.
Note: Unbuffered channels will always return 0 because they cannot hold elements.
Inputs
c: The channel
Returns:
- Number of elements
Example:
import "core:sync/chan"
import "core:fmt"
cap_example :: proc() {
c, _ := chan.create(chan.Chan(int), 2, context.allocator)
defer chan.destroy(c)
fmt.println(chan.cap(c))
}Output:
2close
close :: proc(c: ^Raw_Chan) -> (bool)SourceCloses the channel, preventing new messages from being added.
Inputs
c: The channel
Returns:
trueif the channel was closed by this operation,falseif it was already closed
Example:
import "core:sync/chan"
close_example :: proc() {
c, _ := chan.create(chan.Chan(int), 2, context.allocator)
defer chan.destroy(c)
// Sending a message to an open channel
assert(chan.send(c, 1), "allowed to send")
// Closing the channel successfully
assert(chan.close(c), "successfully closed")
// Trying to send a message after the channel is closed (should fail)
assert(!chan.send(c, 1), "not allowed to send after close")
// Trying to close the channel again (should fail since it's already closed)
assert(!chan.close(c), "was already closed")
}create_buffered
create_buffered :: proc(C: typeid, cap: int, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)SourceCreates a buffered version of the specified Chan type.
Allocates Using Provided Allocator
Inputs
Returns:
- The initialized
Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_buffered_example :: proc() {
c, err := chan.create_buffered(chan.Chan(int), 10, context.allocator)
assert(err == .None)
defer chan.destroy(c)
}create_raw_buffered
create_raw_buffered :: proc(any_int, msg_alignment: int, cap: int, allocator: runtime.Allocator, loc = #caller_location) -> (c: ^Raw_Chan, err: runtime.Allocator_Error)SourceCreates a buffered Raw_Chan for messages of the specified size and alignment.
Allocates Using Provided Allocator
Inputs
msg_size: The size of the messages the messages being sentmsg_alignment: The alignment of the messages being sentcap: The capacity of the channelallocator: The allocator to use
Returns:
- The initialized
Raw_Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_raw_unbuffered_example :: proc() {
c, err := chan.create_raw_buffered(size_of(int), align_of(int), 10, context.allocator)
assert(err == .None)
defer chan.destroy(c)
}create_raw_unbuffered
create_raw_unbuffered :: proc(any_int, msg_alignment: int, allocator: runtime.Allocator, loc = #caller_location) -> (c: ^Raw_Chan, err: runtime.Allocator_Error)SourceCreates an unbuffered Raw_Chan for messages of the specified size and alignment.
Allocates Using Provided Allocator
Inputs
msg_size: The size of the messages the messages being sentmsg_alignment: The alignment of the messages being sentallocator: The allocator to use
Returns:
- The initialized
Raw_Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_raw_unbuffered_example :: proc() {
unbuffered, err := chan.create_raw(size_of(int), align_of(int), context.allocator)
assert(err == .None)
defer chan.destroy(unbuffered)
}create_unbuffered
create_unbuffered :: proc(C: typeid, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)SourceCreates an unbuffered version of the specified Chan type.
Allocates Using Provided Allocator
Inputs
$C: Type ofChanto createallocator: The allocator to use
Returns:
- The initialized
Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_unbuffered_example :: proc() {
c, err := chan.create_unbuffered(chan.Chan(int), context.allocator)
assert(err == .None)
defer chan.destroy(c)
}destroy
destroy :: proc(c: ^Raw_Chan, loc = #caller_location) -> (err: runtime.Allocator_Error)SourceDestroys the Channel.
Inputs
c: The channel to destroy
Returns:
- An
Allocator_Error
is_buffered
is_buffered :: proc(c: ^Raw_Chan) -> (bool)SourceChecks if the given channel is buffered.
Inputs
c: The channel
Returns:
trueif the channel is buffered,falseotherwise
Example:
import "core:sync/chan"
is_buffered_example :: proc() {
c, _ := chan.create(chan.Chan(int), 1, context.allocator)
defer chan.destroy(c)
assert(chan.is_buffered(c))
}is_closed
is_closed :: proc(c: ^Raw_Chan) -> (bool)SourceReturns if the channel is closed or not
Inputs
c: The channel
Returns:
trueif the channel is closed,falseotherwise
is_unbuffered
is_unbuffered :: proc(c: ^Raw_Chan) -> (bool)SourceChecks if the given channel is unbuffered.
Inputs
c: The channel
Returns:
trueif the channel is unbuffered,falseotherwise
Example:
import "core:sync/chan"
is_buffered_example :: proc() {
c, _ := chan.create(chan.Chan(int), context.allocator)
defer chan.destroy(c)
assert(chan.is_unbuffered(c))
}len
len :: proc(c: ^Raw_Chan) -> (int)SourceReturns the number of elements currently in the channel.
Note: Unbuffered channels will always return 0 because they cannot hold elements.
Inputs
c: The channel
Returns:
- Number of elements
Example:
import "core:sync/chan"
import "core:fmt"
len_example :: proc() {
c, _ := chan.create(chan.Chan(int), 2, context.allocator)
defer chan.destroy(c)
fmt.println(chan.len(c))
assert(chan.send(c, 1)) // add an element
fmt.println(chan.len(c))
}Output:
0
1recv
recv :: proc(c: C) -> (data: T, ok: bool)SourceReads a message from the channel, blocking the current thread if:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to or the channel is closed. recv will return false when attempting to receive a message on an already closed channel.
Inputs
c: The channel
Returns
- The message
trueif a message was received,falsewhen the channel was already closed
Example:
import "core:sync/chan"
recv_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
assert(chan.send(c, 2))
value, ok := chan.recv(c)
assert(ok, "the value was received")
// this would block since the channel is now empty
// value, ok = chan.recv(c)
// reading from a closed channel returns false
chan.close(c)
value, ok = chan.recv(c)
assert(!ok, "the channel is closed")
}recv_raw
recv_raw :: proc(c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool)SourceReads a message from the channel, blocking the current thread if:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to or the channel is closed. recv_raw will return false when attempting to receive a message on an already closed channel.
Note: The location pointed to by msg_out must match the size and alignment used when the Raw_Chan was created.
Inputs
c: The channelmsg_out: Pointer to where the message should be stored
Returns
trueif a message was received,falsewhen the channel was already closed
Example:
import "core:sync/chan"
recv_raw_example :: proc() {
c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
value := 2
assert(chan.send_raw(c, &value))
assert(chan.recv_raw(c, &value))
// this would block since the channel is now empty
// assert(chan.recv_raw(c, &value))
// reading from a closed channel returns false
chan.close(c)
assert(! chan.recv_raw(c, &value))
}select_raw
select_raw :: proc(recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status)Sourcesend
send :: proc(c: C, data: T) -> (ok: bool)SourceSends the specified message, blocking the current thread if:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from or the channel is closed. send will return false when attempting to send on an already closed channel.
Inputs
c: The channeldata: The message to send
Returns
trueif the message was sent,falsewhen the channel was already closed
Example:
import "core:sync/chan"
send_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
assert(chan.send(c, 2))
// this would block since the channel has a buffersize of 1
// assert(chan.send(c, 2))
// sending on a closed channel returns false
chan.close(c)
assert(! chan.send(c, 2))
}send_raw
send_raw :: proc(c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)SourceSends the specified message, blocking the current thread if:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from or the channel is closed. send_raw will return false when attempting to send on an already closed channel.
Note: The message referenced by msg_out must match the size and alignment used when the Raw_Chan was created.
Inputs
c: The channelmsg_out: Pointer to the data to send
Returns
trueif the message was sent,falsewhen the channel was already closed
Example:
import "core:sync/chan"
send_raw_example :: proc() {
c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
value := 2
assert(chan.send_raw(c, &value))
// this would block since the channel has a buffersize of 1
// assert(chan.send_raw(c, &value))
// sending on a closed channel returns false
chan.close(c)
assert(! chan.send_raw(c, &value))
}try_recv
try_recv :: proc(c: C) -> (data: T, ok: bool)SourceTries reading a message from the channel in a non-blocking fashion.
Inputs
c: The channel
Returns
- The message
trueif a message was received,falsewhen the channel was already closed or no message was available
Example:
import "core:sync/chan"
try_recv_example :: proc() {
c, err := chan.create(chan.Chan(int), context.allocator)
assert(err == .None)
defer chan.destroy(c)
_, ok := chan.try_recv(c)
assert(!ok, "there is not value to read")
}try_recv_raw
try_recv_raw :: proc(c: ^Raw_Chan, msg_out: rawptr) -> (bool)SourceReads a message from the channel if one is available.
Note: The location pointed to by msg_out must match the size and alignment used when the Raw_Chan was created.
Inputs
c: The channelmsg_out: Pointer to where the message should be stored
Returns
trueif a message was received,falsewhen the channel was already closed or no message was available
Example:
import "core:sync/chan"
try_recv_raw_example :: proc() {
c, err := chan.create_raw(size_of(int), align_of(int), context.allocator)
assert(err == .None)
defer chan.destroy(c)
value: int
assert(!chan.try_recv_raw(c, &value))
}try_select_raw
try_select_raw :: proc(recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status)SourceAttempts to either send or receive messages on the specified channels without blocking.
try_select_raw first identifies which channels have messages ready to be received and which are available for sending. It then randomly selects one operation (either a send or receive) to perform.
If no channels have messages ready, the procedure is a noop.
Note: Each message in send_msgs corresponds to the send channel at the same index in sends. If the message is nil, corresponding send channel will be skipped.
Inputs
recv: A slice of channels to read fromsends: A slice of channels to send messages onsend_msgs: A slice of messages to sendrecv_out: A pointer to the location where, when receiving, the message should be stored
Returns
- Position of the available channel which was used for receiving or sending
trueif sending/receiving was successfull,falseif the channel was closed or no channel was available
Example:
import "core:sync/chan"
import "core:fmt"
try_select_raw_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
// sending value '1' on the channel
value1 := 1
msgs := [?]rawptr{&value1}
send_chans := [?]^chan.Raw_Chan{c}
// for simplicity the same channel used for sending is also used for receiving
receive_chans := [?]^chan.Raw_Chan{c}
// where the value from the read should be stored
received_value: int
idx, ok := chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value)
idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value)
// closing of a channel also affects the select operation
chan.close(c)
idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok)
}Output:
SELECT: 0 Send
RECEIVED VALUE 0
SELECT: 0 Recv
RECEIVED VALUE 1
SELECT: -1 Nonetry_send
try_send :: proc(c: C, data: T) -> (ok: bool)SourceTries sending the specified message which is:
- blocking: given the channel is unbuffered
- non-blocking: given the channel is buffered
Inputs
c: The channeldata: The message to send
Returns
trueif the message was sent,falsewhen the channel was
already closed or the channel's buffer was full
Example:
import "core:sync/chan"
try_send_example :: proc() {
c, err := chan.create(chan.Chan(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
assert(chan.try_send(c, 2), "there is enough space")
assert(!chan.try_send(c, 2), "the buffer is already full")
}try_send_raw
try_send_raw :: proc(c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)SourceTries sending the specified message which is:
- blocking: given the channel is unbuffered
- non-blocking: given the channel is buffered
Note: The message referenced by msg_out must match the size and alignment used when the Raw_Chan was created.
Inputs
c: the channelmsg_out: pointer to the data to send
Returns
trueif the message was sent,falsewhen the channel was
already closed or the channel's buffer was full
Example:
import "core:sync/chan"
try_send_raw_example :: proc() {
c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
assert(err == .None)
defer chan.destroy(c)
value := 2
assert(chan.try_send_raw(c, &value), "there is enough space")
assert(!chan.try_send_raw(c, &value), "the buffer is already full")
}Procedure Groups
2create
create :: proc{create_unbuffered, create_buffered}SourceCreates a buffered or unbuffered Chan instance.
Allocates Using Provided Allocator
Inputs
$C: Type ofChanto create- [
cap: The capacity of the channel] omit for creating unbuffered channels allocator: The allocator to use
Returns:
- The initialized
Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_example :: proc() {
unbuffered: chan.Chan(int)
buffered: chan.Chan(int)
err: runtime.Allocator_Error
unbuffered, err = chan.create(chan.Chan(int), context.allocator)
assert(err == .None)
defer chan.destroy(unbuffered)
buffered, err = chan.create(chan.Chan(int), 10, context.allocator)
assert(err == .None)
defer chan.destroy(buffered)
}create_raw
create_raw :: proc{create_raw_unbuffered, create_raw_buffered}SourceCreates a buffered or unbuffered Raw_Chan for messages of the specified size and alignment.
Allocates Using Provided Allocator
Inputs
msg_size: The size of the messages the messages being sentmsg_alignment: The alignment of the messages being sent- [
cap: The capacity of the channel] omit for creating unbuffered channels allocator: The allocator to use
Returns:
- The initialized
Raw_Chan - An
Allocator_Error
Example:
import "core:sync/chan"
create_raw_example :: proc() {
unbuffered: ^chan.Raw_Chan
buffered: ^chan.Raw_Chan
err: runtime.Allocator_Error
unbuffered, err = chan.create_raw(size_of(int), align_of(int), context.allocator)
assert(err == .None)
defer chan.destroy(unbuffered)
buffered, err = chan.create_raw(size_of(int), align_of(int), 10, context.allocator)
assert(err == .None)
defer chan.destroy(buffered)
}