_Bool __go_receive_acquire (struct __go_channel *channel, _Bool for_select) { int i; _Bool my_wait_lock; _Bool synched_with_select; my_wait_lock = 0; synched_with_select = 0; i = pthread_mutex_lock (&channel->lock); __go_assert (i == 0); while (1) { _Bool need_broadcast; need_broadcast = 0; /* Check whether the channel is closed. */ if (channel->is_closed && (channel->num_entries == 0 ? channel->next_store == 0 : channel->next_fetch == channel->next_store)) { channel->selected_for_receive = 0; __go_unlock_and_notify_selects (channel); return 0; } /* If somebody else has the channel locked for receiving, we have to wait. If FOR_SELECT is true, then we are the one with the lock. */ if (!channel->selected_for_receive || for_select) { if (channel->num_entries == 0) { /* If somebody else is waiting to receive, we have to wait. */ if (!channel->waiting_to_receive || my_wait_lock) { _Bool was_marked; /* Lock the channel so that we get to receive next. */ was_marked = channel->waiting_to_receive; channel->waiting_to_receive = 1; my_wait_lock = 1; /* See if there is a value to receive. */ if (channel->next_store > 0) return 1; /* If we haven't already done so, try to synch with a select waiting to send on this channel. If we have already synched with a select, we are just looping until the select eventually causes something to be sent. */ if (!synched_with_select && !for_select) { if (__go_synch_with_select (channel, 0)) { synched_with_select = 1; need_broadcast = 1; } } /* If we marked the channel as waiting, we need to signal, because something changed. It needs to be a broadcast since there might be other receivers waiting. */ if (!was_marked) { i = pthread_cond_broadcast (&channel->cond); __go_assert (i == 0); } } } else { /* If there is a value on the channel, we are OK. */ if (channel->next_fetch != channel->next_store) return 1; } } /* If we just synched with a select, then we need to signal the select condition variable. We can only do that if we unlock the channel. So we need to unlock, signal, lock, and go around the loop again without waiting. */ if (need_broadcast) { __go_broadcast_to_select (channel); continue; } /* Wait for something to change, then loop around and try again. */ i = pthread_cond_wait (&channel->cond, &channel->lock); __go_assert (i == 0); } }
_Bool __go_send_nonblocking_acquire (struct __go_channel *channel) { int i; _Bool has_space; i = pthread_mutex_lock (&channel->lock); __go_assert (i == 0); while (channel->selected_for_send) { i = pthread_cond_wait (&channel->cond, &channel->lock); __go_assert (i == 0); } if (channel->is_closed) { i = pthread_mutex_unlock (&channel->lock); __go_assert (i == 0); __go_panic_msg ("send on closed channel"); } if (channel->num_entries > 0) has_space = ((channel->next_store + 1) % channel->num_entries != channel->next_fetch); else { /* This is a synchronous channel. If somebody is current sending, then we can't send. Otherwise, see if somebody is waiting to receive, or see if we can synch with a select. */ if (channel->waiting_to_send) { /* Some other goroutine is currently sending on this channel, which means that we can't. */ has_space = 0; } else if (channel->waiting_to_receive) { /* Some other goroutine is waiting to receive a value, so we can send directly to them. */ has_space = 1; } else if (__go_synch_with_select (channel, 1)) { /* We found a select waiting to receive data, so we can send to that. */ __go_broadcast_to_select (channel); has_space = 1; } else { /* Otherwise, we can't send, because nobody is waiting to receive. */ has_space = 0; } if (has_space) { channel->waiting_to_send = 1; __go_assert (channel->next_store == 0); } } if (!has_space) { i = pthread_mutex_unlock (&channel->lock); __go_assert (i == 0); return 0; } return 1; }
void __go_send_release (struct __go_channel *channel) { int i; if (channel->num_entries != 0) { /* This is a buffered channel. Bump the store count and signal the condition variable. */ channel->next_store = (channel->next_store + 1) % channel->num_entries; i = pthread_cond_signal (&channel->cond); __go_assert (i == 0); } else { _Bool synched_with_select; /* This is a synchronous channel. Indicate that we have a value waiting. */ channel->next_store = 1; channel->waiting_to_send = 1; /* Tell everybody else to do something. This has to be a broadcast because we might have both senders and receivers waiting on the condition, but senders won't send another signal. */ i = pthread_cond_broadcast (&channel->cond); __go_assert (i == 0); /* Wait until the value is received. */ synched_with_select = 0; while (1) { if (channel->next_store == 0) break; /* If nobody is currently waiting to receive, try to synch up with a select. */ if (!channel->waiting_to_receive && !synched_with_select) { if (__go_synch_with_select (channel, 1)) { synched_with_select = 1; __go_broadcast_to_select (channel); continue; } } i = pthread_cond_wait (&channel->cond, &channel->lock); __go_assert (i == 0); } channel->waiting_to_send = 0; /* Using the mutexes should implement a memory barrier. */ /* We have to signal again since we cleared the waiting_to_send field. This has to be a broadcast because both senders and receivers might be waiting, but only senders will be able to act. */ i = pthread_cond_broadcast (&channel->cond); __go_assert (i == 0); } channel->selected_for_send = 0; __go_unlock_and_notify_selects (channel); }