互斥
有时两个或多个线程可能都需要访问某种全局资源。由于全局资源的当前状态可能被一个线程修改,并且该修改的值在被某个其它线程使用时可能是不可预测的,因此这可能产生错误的结果。举一个简单的例子,看看这段代码:
$i = 0
a = Thread.new {
1000000.times{ $i += 1 }
}
b = Thread.new {
1000000.times{ $i += 1 }
}
a.join
b.join
puts( $i )
我的目的是运行两个线程,每个线程递增全局变量 $i
一百万次。在这结束时 $i
的预期结果(自然)将是 200 万。但是,事实上,当我运行它时,$i
的结束值是 1088237(你可能会看到不同的结果)。
对此的解释是,这两个线程实际上正在竞争对全局变量 $i
的访问。这意味着,在某些时候,线程 a
可能获得 $i
的当前值(假设它恰好是 100)并且同时线程 b
也获得 $i
的当前值(仍然是 100)。现在,a
增加它刚刚获得的值($i
变为 101)并且 b
增加它刚刚获得的值(因此 $i
再次变为 101!)。换句话说,当多个线程同时访问共享资源时,其中一些线程可能正在使用过时的值 - 即,没有考虑其它线程对该资源所做的任何修改。随着时间的推移,这些操作产生的错误会累积,直到我们得出的结果与我们预期的结果大不相同。
为了解决这个问题,我们需要确保当一个线程可以访问全局资源时,它会阻止其它线程的访问。另一种说法,即授予多个线程对全局资源的访问应该是“互斥的”(mutually exclusive)。你可以使用 Ruby 的 Mutex 类来实现它,该类使用信号量来指示当前资源是否正在被访问,并提供同步方法以防止(外部)访问块内的资源。请注意,你必须引入(require)‘thread’ 才能使用 Mutex 类。这是我重写的代码:
require 'thread'
$i = 0
semaphore = Mutex.new
a = Thread.new {
semaphore.synchronize{
1000000.times{ $i += 1 }
}
}
b = Thread.new {
semaphore.synchronize{
1000000.times{ $i += 1 }
}
}
a.join
b.join
puts( $i )
这次,$i
的最终结果是 2000000。
最后,有关使用 Threads 的更有用的示例,请查看 file_find2.rb。此示例程序使用 Ruby 的 Find 类遍历磁盘上的目录。有关非线程示例,请参阅 file_find.rb。将其与第 13 章中的 file_info3.rb 程序进行比较,其使用的 Dir 类。
这会设置两个线程运行。第一个,t1
,调用 processFiles
方法来查找和显示文件信息(你需要编辑对 processFiles
的调用以将系统上的目录名传递给它)。第二个线程 t2
只打印出一条消息,当 t1
处于“活着”状态(即运行或休眠)时,该线程运行:
t1 = Thread.new{
Thread.stop
processFiles( '..' ) # edit this directory name
}
t2 = Thread.new{
Thread.stop
while t1.alive? do
print( "\n\t\tProcessing..." )
Thread.pass
end
}
每个线程使用 Thread.pass
完成让步控制(t1
线程在 processFiles
方法内进行让步控制)。在实际应用程序中,你可以采用此技术以提供某种类型的用户反馈,同时进行一些密集的过程(例如目录遍历)。