123456789_123456789_123456789_123456789_123456789_

Class: Thread::ConditionVariable

Relationships & Source Files
Inherits: Object
Defined in: thread_sync.c,
thread_sync.c

Overview

ConditionVariable objects augment class Mutex. Using condition variables, it is possible to suspend while in the middle of a critical section until a condition is met, such as a resource becomes available.

Due to non-deterministic scheduling and spurious wake-ups, users of condition variables should always use a separate boolean predicate (such as reading from a boolean variable) to check if the condition is actually met before starting to wait, and should wait in a loop, re-checking the condition every time the ConditionVariable is waken up. The idiomatic way of using condition variables is calling the #wait method in an until loop with the predicate as the loop condition.

condvar.wait(mutex) until condition_is_met

In the example below, we use the boolean variable resource_available (which is protected by mutex) to indicate the availability of the resource, and use condvar to wait for that variable to become true. Note that:

  1. ::Thread b may be scheduled before thread a1 and a2, and may run so fast that it have already made the resource available before either a1 or a2 starts. Therefore, a1 and a2 should check if resource_available is already true before starting to wait.

  2. The #wait method may spuriously wake up without signalling. Therefore, thread a1 and a2 should recheck resource_available after the #wait method returns, and go back to wait if the condition is not actually met.

  3. It is possible that thread a2 starts right after thread a1 is waken up by b. Thread a2 may have acquired the mutex and consumed the resource before thread a1 acquires the mutex. This necessitates rechecking after #wait, too.

Example:

mutex = Thread::Mutex.new

resource_available = false
condvar = Thread::ConditionVariable.new

a1 = Thread.new {
  # Thread 'a1' waits for the resource to become available and consumes
  # the resource.
  mutex.synchronize {
    condvar.wait(mutex) until resource_available
    # After the loop, 'resource_available' is guaranteed to be true.

    resource_available = false
    puts "a1 consumed the resource"
  }
}

a2 = Thread.new {
  # Thread 'a2' behaves like 'a1'.
  mutex.synchronize {
    condvar.wait(mutex) until resource_available
    resource_available = false
    puts "a2 consumed the resource"
  }
}

b = Thread.new {
  # Thread 'b' periodically makes the resource available.
  loop {
    mutex.synchronize {
      resource_available = true

      # Notify one waiting thread if any.  It is possible that neither
      # 'a1' nor 'a2 is waiting on 'condvar' at this moment.  That's OK.
      condvar.signal
    }
    sleep 1
  }
}

# Eventually both 'a1' and 'a2' will have their resources, albeit in an
# unspecified order.
[a1, a2].each {|th| th.join}

Class Method Summary

  • .new constructor

    Creates a new condition variable instance.

Instance Method Summary

Constructor Details

.new

Creates a new condition variable instance.

[ GitHub ]

  
# File 'thread_sync.c', line 1557

static VALUE
rb_condvar_initialize(VALUE self)
{
    struct rb_condvar *cv = condvar_ptr(self);
    ccan_list_head_init(&cv->waitq);
    return self;
}

Instance Method Details

#broadcast

Wakes up all threads waiting for this lock.

[ GitHub ]

  
# File 'thread_sync.c', line 1633

static VALUE
rb_condvar_broadcast(VALUE self)
{
    struct rb_condvar *cv = condvar_ptr(self);
    wakeup_all(&cv->waitq);
    return self;
}

#marshal_dump

This method is for internal use only.

Alias for Queue#marshal_dump.

#signal

Wakes up the first thread in line waiting for this lock.

[ GitHub ]

  
# File 'thread_sync.c', line 1619

static VALUE
rb_condvar_signal(VALUE self)
{
    struct rb_condvar *cv = condvar_ptr(self);
    wakeup_one(&cv->waitq);
    return self;
}

#wait(mutex, timeout = nil)

Releases the lock held in mutex and waits; reacquires the lock on wakeup.

If timeout is given, this method returns after timeout seconds passed, even if no other thread doesn’t signal.

This method may wake up spuriously due to underlying implementation details.

Returns the slept result on mutex.

[ GitHub ]

  
# File 'thread_sync.c', line 1593

static VALUE
rb_condvar_wait(int argc, VALUE *argv, VALUE self)
{
    rb_execution_context_t *ec = GET_EC();

    struct rb_condvar *cv = condvar_ptr(self);
    struct sleep_call args;

    rb_scan_args(argc, argv, "11", &args.mutex, &args.timeout);

    struct sync_waiter sync_waiter = {
        .self = args.mutex,
        .th = ec->thread_ptr,
        .fiber = nonblocking_fiber(ec->fiber_ptr)
    };

    ccan_list_add_tail(&cv->waitq, &sync_waiter.node);
    return rb_ensure(do_sleep, (VALUE)&args, delete_from_waitq, (VALUE)&sync_waiter);
}