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

Condition Variables

Using a mutex to protect critical data is sometimes not enough. Suppose you are in a critical section, but you need to wait for some particular resource. If your thread goes to sleep waiting for this resource, it is possible that no other thread will be able to release the resource because it cannot enter the critical section---the original process still has it locked. You need to be able to give up temporarily your exclusive use of the critical region and simultaneously tell people that you're waiting for a resource. When the resource becomes available, you need to be able to grab it andreobtain the lock on the critical region, all in one step.

This is where condition variables come in. A condition variable is simply a semaphore that is associated with a resource and is used within the protection of a particular mutex. When you need a resource that's unavailable, you wait on a condition variable. That action releases the lock on the corresponding mutex. When some other thread signals that the resource is available, the original thread comes off the wait and simultaneously regains the lock on the critical region.

require 'thread'

mutex = Mutex.new

cv = ConditionVariable.new

a = Thread.new {

  mutex.synchronize {

    puts "A: I have critical section, but will wait for cv"

    cv.wait(mutex)

    puts "A: I have critical section again! I rule!"

  }

}

puts "(Later, back at the ranch...)"

b = Thread.new {

  mutex.synchronize {

    puts "B: Now I am critical, but am done with cv"

    cv.signal

    puts "B: I am still critical, finishing up"

  }

}

a.join

b.join

produces:

A: I have critical section, but will wait for cv(Later, back at the ranch...)

B: Now I am critical, but am done with cv

B: I am still critical, finishing up

A: I have critical section again! I rule!

For alternative implementations of synchronization mechanisms, see monitor.rbandsync.rbin thelibsubdirectory of the distribution.

Running Multiple Processes

Sometimes you may want to split a task into several process-sized chunks---or perhaps you need to run a separate process that was not written in Ruby. Not a problem: Ruby has a number of methods by which you may spawn and manage separate processes.

Spawning New Processes

There are several ways to spawn a separate process; the easiest is to run some command and wait for it to complete. You might find yourself doing this to run some separate command or retrieve data from the host system. Ruby does this for you with the systemand backquote methods.

system("tar xzf test.tgz")

»

tar: test.tgz: Cannot open: No such file or directory\ntar: Error is not recoverable: exiting now\ntar: Child returned status 2\ntar: Error exit delayed from previous errors\nfalse

result = `date`

result

»

"Sun Nov 25 23:43:35 CST 2001\n"

The method Kernel::system executes the given command in a subprocess; it returnstrueif the command was found and executed properly,falseotherwise. In case of failure, you'll find the subprocess's exit code in the global variable$?.

One problem with systemis that the command's output will simply go to the same destination as your program's output, which may not be what you want. To capture the standard output of a subprocess, you can use the backquotes, as with`date`in the previous example. Remember that you may need to useString#chomp to remove the line-ending characters from the result.

Okay, this is fine for simple cases---we can run some other process and get the return status. But many times we need a bit more control than that. We'd like to carry on a conversation with the subprocess, possibly sending it data and possibly getting some back. The method IO.popen does just this. Thepopenmethod runs a command as a subprocess and connects that subprocess's standard input and standard output to a RubyIOobject. Write to theIOobject, and the subprocess can read it on standard input. Whatever the subprocess writes is available in the Ruby program by reading from theIOobject.

For example, on our systems one of the more useful utilities is pig, a program that reads words from standard input and prints them in pig Latin (or igpay atinlay). We can use this when our Ruby programs need to send us output that our 5-year-olds shouldn't be able to understand.

pig = IO.popen("pig", "w+")

pig.puts "ice cream after they go to bed"

pig.close_write

puts pig.gets

produces:

iceway eamcray afterway eythay ogay otay edbay

This example illustrates both the apparent simplicity and the real-world complexities involved in driving subprocesses through pipes. The code certainly looks simple enough: open the pipe, write a phrase, and read back the response. But it turns out that the pigprogram doesn't flush the output it writes. Our original attempt at this example, which had apig.putsfollowed by apig.gets, hung forever. Thepigprogram processed our input, but its response was never written to the pipe. We had to insert thepig.close_writeline. This sends an end-of-file topig's standard input, and the output we're looking for gets flushed aspigterminates.

There's one more twist to popen. If the command you pass it is a single minus sign (``--''),popenwill fork a new Ruby interpreter. Both this and the original interpreter will continue running by returning from thepopen. The original process will receive anIOobject back, while the child will receivenil.

pipe = IO.popen("-","w+")

if pipe

  pipe.puts "Get a job!"

  $stderr.puts "Child says '#{pipe.gets.chomp}'"

else

  $stderr.puts "Dad says '#{gets.chomp}'"

  puts "OK"

end

produces:

Dad says 'Get a job!'

Child says 'OK'

In addition to popen, the traditional Unix callsKernel::fork ,Kernel::exec , andIO.pipe are available on platforms that support them. The file-naming convention of manyIOmethods andKernel::open will also spawn subprocesses if you put a ``|'' as the first character of the filename (see the introduction to classIOon page 325 for details). Note that youcannotcreate pipes usingFile.new ; it's just for files.

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