Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ruby / Yukihiro Matsumoto_Programming Ruby.doc
Скачиваний:
144
Добавлен:
06.06.2015
Размер:
2.71 Mб
Скачать

Controlling the Thread Scheduler

In a well-designed application, you'll normally just let threads do their thing; building timing dependencies into a multithreaded application is generally considered to be bad form.

However, there are times when you need to control threads. Perhaps the jukebox has a thread that displays a light show. We might need to stop it temporarily when the music stops. You might have two threads in a classic producer-consumer relationship, where the consumer has to pause if the producer gets backlogged.

Class Threadprovides a number of methods to control the thread scheduler. InvokingThread.stop stops the current thread, whileThread#run arranges for a particular thread to be run.Thread.pass deschedules the current thread, allowing others to run, andThread#join andThread#value suspend the calling thread until a given thread finishes.

We can demonstrate these features in the following, totally pointless program.

t = Thread.new { sleep .1; Thread.pass; Thread.stop; }

t.status

»

"sleep"

t.run

t.status

»

"run"

t.run

t.status

»

false

However, using these primitives to achieve any kind of real synchronization is, at best, hit or miss; there will always be race conditions waiting to bite you. And when you're working with shared data, race conditions pretty much guarantee long and frustrating debugging sessions. Fortunately, threads have one additional facility---the idea of a critical section. Using this, we can build a number of secure synchronization schemes.

Mutual Exclusion

The lowest-level method of blocking other threads from running uses a global ``thread critical'' condition. When the condition is set to true(using theThread.critical= method), the scheduler will not schedule any existing thread to run. However, this does not block new threads from being created and run. Certain thread operations (such as stopping or killing a thread, sleeping in the current thread, or raising an exception) may cause a thread to be scheduled even when in a critical section.

Using Thread.critical= directly is certainly possible, but it isn't terribly convenient. Fortunately, Ruby comes packaged with several alternatives. Of these, two of the best, classMutexand classConditionVariable, are available in thethreadlibrary module; see the documentation beginning on page 457.

The Mutex Class

Mutexis a class that implements a simple semaphore lock for mutually exclusive access to some shared resource. That is, only one thread may hold the lock at a given time. Other threads may choose to wait in line for the lock to become available, or may simply choose to get an immediate error indicating that the lock is not available.

A mutex is often used when updates to shared data need to be atomic. Say we need to update two variables as part of a transaction. We can simulate this in a trivial program by incrementing some counters. The updates are supposed to be atomic---the outside world should never see the counters with different values. Without any kind of mutex control, this just doesn't work.

count1 = count2 = 0

difference = 0

counter = Thread.new do

  loop do

    count1 += 1

    count2 += 1

  end

end

spy = Thread.new do

  loop do

    difference += (count1 - count2).abs

  end

end

sleep 1

Thread.critical = 1

count1

»

189263

count2

»

189263

difference

»

90500

This example shows that the ``spy'' thread woke up a large number of times and found the values of count1andcount2inconsistent.

Fortunately we can fix this using a mutex.

require 'thread'

mutex = Mutex.new

count1 = count2 = 0

difference = 0

counter = Thread.new do

  loop do

    mutex.synchronize do

      count1 += 1

      count2 += 1

    end

  end

end

spy = Thread.new do

  loop do

    mutex.synchronize do

      difference += (count1 - count2).abs

    end

  end

end

sleep 1

mutex.lock

count1

»

17075

count2

»

17075

difference

»

0

By placing all accesses to the shared data under control of a mutex, we ensure consistency. Unfortunately, as you can see from the numbers, we also suffer quite a performance penalty.

Соседние файлы в папке Ruby