30.md 25.4 KB
Newer Older
W
init  
wizardforcel 已提交
1 2
## 21.多线程

W
wizardforcel 已提交
3
通常,程序是逐行读取的,并由计算机逐步执行。 在任何给定的时间点,计算机仅执行一条指令 1。 随着技术的进步,可以同时执行许多指令,这种同时执行许多操作的过程称为多处理或并行处理。 想象一下,你要吃 5 个披萨。 你需要花费很长时间。 如果你也可以带来你的朋友,那么你的人们可以分担负担。 如果你可以组成一个由 20 个人组成的小组,那么吃 5 个披萨就变得像吃零食一样容易。 完成分配任务所需的时间大大减少。
W
init  
wizardforcel 已提交
4

W
wizardforcel 已提交
5
在 Ruby 编程中,你可以使解释器以并行方式执行代码。 并行执行代码的过程称为多线程。 要显示多线程的工作方式,请在文本编辑器中键入以下程序并执行。
W
init  
wizardforcel 已提交
6

W
wizardforcel 已提交
7
```rb
W
init  
wizardforcel 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#!/usr/bin/ruby
# multithreading.rb

a = Thread.new{
  i = 1
  while i<=10
    sleep(1)
    puts i
    i += 1
  end
}
puts "This code comes after thread"
a.join
```

这是程序输出

W
wizardforcel 已提交
25
```rb
W
init  
wizardforcel 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
This code comes after thread
1
2
3
4
5
6
7
8
9
10
```

与其他程序不同,此程序将花费 10 秒钟执行。 看一下程序和输出。 `puts "This code comes after thread"`之后

W
wizardforcel 已提交
41
```rb
W
init  
wizardforcel 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
a = Thread.new{
  i = 1;
  while i<=10
    sleep(1)
    puts i
    i += 1
  end
}
```

但是它首先被打印。 在上面显示的语句中,我们创建一个名为 a 的新线程,在该线程中,我们使用`while`循环从 1 到 10 进行打印。请注意,我们调用了一个名为`sleep(1)`的函数,该函数可使进程休眠或保持空闲一秒钟。 创建了一个线程,当线程运行时,Ruby 解释器检查主线程,并且遇到`puts "This code comes after thread"`,因此它在执行我们创建的新线程 a 的同时打印出字符串并并行执行,因此 1 到 10 在插入`sleep(1)`时被打印,每个循环执行大约需要 1 秒钟。 因此,在 10 秒钟后,线程 a 完成。

`a.join`告诉 Ruby 解释器等待线程完成执行。 一旦执行了`a.join`(如果有)之后的语句,线程 a 的执行结束。 由于之后没有语句,程序终止。

这是另一个清楚说明多线程的程序。 仔细检查该程序,尝试了解它的工作原理,稍后我将对其进行解释

W
wizardforcel 已提交
58
```rb
W
init  
wizardforcel 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
#!/usr/bin/ruby
# multithreading_1.rb

def func1
  i=0
  while i<=2
    puts "func1 at: #{Time.now}"
    sleep(2)
    i=i+1
  end
end

def func2
  j=0
  while j<=2
    puts "func2 at: #{Time.now}"
    sleep(1)
    j=j+1
  end
end

puts "Started At #{Time.now}"
t1 = Thread.new{func1()}
t2 = Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"
```

看一下突出显示的代码,我们创建了两个线程`t1``t2`。 在线程`t1`中,我们称为方法`func1()`,在线程`t2`中,我们称为方法`func2()`,这样做是同时执行`func1()``func2()`。 执行后,输出结果将如下所示: &lt;sup class="footnote"&gt;[ [51](#_footnotedef_51 "View footnote.") ]&lt;/sup&gt;

W
wizardforcel 已提交
90
```rb
W
init  
wizardforcel 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104
Started At Sun Apr 25 09:37:51 +0530 2010
func1 at: Sun Apr 25 09:37:51 +0530 2010
func2 at: Sun Apr 25 09:37:51 +0530 2010
func2 at: Sun Apr 25 09:37:52 +0530 2010
func1 at: Sun Apr 25 09:37:53 +0530 2010
func2 at: Sun Apr 25 09:37:53 +0530 2010
func1 at: Sun Apr 25 09:37:55 +0530 2010
End at Sun Apr 25 09:37:57 +0530 2010
```

从输出中可以看到,`func1``func2`打印的输出是隔行扫描的,这证明它们是并行执行的。 请注意,在`func1`中,通过提供`sleep(2)`使线程休眠 2 秒,在`func2`中,通过提供`sleep(1)`使线程休眠 1 秒。

我对 [multithreading_1.rb](code/multithreading_1.rb) 进行了一些小的更改,以产生 [multithreading_2.rb](code/multithreading_2.rb) ,该结果几乎与 [multithreading_1.rb](code/multithreading_1.rb) 的结果相同,因此这里是其代码:

W
wizardforcel 已提交
105
```rb
W
init  
wizardforcel 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#!/usr/bin/ruby
# multithreading_2.rb

def func name, delay
  i=0
  while i<=2
    puts "#{name} #{Time.now}"
    sleep delay
    i=i+1
  end
end

puts "Started At #{Time.now}"
t1 = Thread.new{func "Thread 1:", 2}
t2 = Thread.new{func "Thread 2:", 3}
t1.join
t2.join
puts "End at #{Time.now}"
```

我没有使用两个函数`func1``func2`,而是编写了一个名为`func`的函数,该函数接受名称和时间延迟作为输入。 其中的循环会打印出传递的名称和执行该语句的时间。 注意这些声明

W
wizardforcel 已提交
128
```rb
W
init  
wizardforcel 已提交
129 130 131 132 133 134
t1 = Thread.new{func "Thread 1:", 2}
t2 = Thread.new{func "Thread 2:", 3}
```

我们创建两个线程`t1``t2`。 在线程`t1`中,我们调用函数`func`并传递名称`Thread 1:`并告诉它休眠 2 秒钟。 在线程`t2`中,我们调用相同的`func`,并传递与`Thread 2:`相同的名称,并在每次循环迭代中告诉它休眠 3 秒。 并在执行程序时产生以下输出

W
wizardforcel 已提交
135
```rb
W
init  
wizardforcel 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
Started At Sun Apr 25 09:44:36 +0530 2010
Thread 1: Sun Apr 25 09:44:36 +0530 2010
Thread 2: Sun Apr 25 09:44:36 +0530 2010
Thread 1: Sun Apr 25 09:44:38 +0530 2010
Thread 2: Sun Apr 25 09:44:39 +0530 2010
Thread 1: Sun Apr 25 09:44:40 +0530 2010
Thread 2: Sun Apr 25 09:44:42 +0530 2010
End at Sun Apr 25 09:44:45 +0530 2010
```

这与 [multithreading_1.rb](code/multithreading_1.rb) 产生的输出非常相似。

### 21.1。 线程变量的范围

线程可以访问主进程中的变量,以以下程序( [thread_variables.rb](code/thread_variables.rb) )为例

W
wizardforcel 已提交
152
```rb
W
init  
wizardforcel 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166
#!/usr/bin/ruby
# thread_variables.rb

variable = 0
puts "Before thread variable = #{variable}"
a = Thread.new{
  variable = 5
}
a.join
puts "After thread variable = #{variable}"
```

输出量

W
wizardforcel 已提交
167
```rb
W
init  
wizardforcel 已提交
168 169 170 171
Before thread variable = 0
After thread variable = 5
```

W
wizardforcel 已提交
172
输入程序并运行。 它将产生上面显示的结果。 从程序中可以看到,在创建线程之前,我们将名为`variable`的变量初始化为 0。 在线程内部,我们将`variable`的值更改为 5。在线程块之后,我们输出的变量值现在为 5。该程序向我们展示了你可以访问和操作在主线程中声明的变量。
W
init  
wizardforcel 已提交
173 174 175

现在让我们看看是否可以在线程范围之外访问在线程内部创建的变量? 键入以下程序( [thread_variables_1.rb](code/thread_variables_1.rb) )并执行

W
wizardforcel 已提交
176
```rb
W
init  
wizardforcel 已提交
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
#!/usr/bin/ruby
# thread_variables_1.rb

variable = 0
puts "Before thread variable = #{variable}"
a = Thread.new{
  variable = 5
  thread_variable = 72
  puts "Inside thread thread_variable = #{thread_variable}"
}
a.join
puts "=================\nAfter thread\nvariable = #{variable}"
puts "thread_variable = #{thread_variable}"
```

Output

W
wizardforcel 已提交
194
```rb
W
init  
wizardforcel 已提交
195 196 197 198 199 200 201 202 203 204
Before thread variable = 0
Inside thread thread_variable = 72
=================
After thread
variable = 5
thread_variables_1.rb:13: undefined local variable or method `thread_variable' for main:Object (NameError)
```

在上面的程序中,我们看到我们在线程`a`中创建了一个名为 thread_variable 的变量,现在我们尝试在以下行中对其进行访问:

W
wizardforcel 已提交
205
```rb
W
init  
wizardforcel 已提交
206 207 208
puts "thread_variable = #{thread_variable}"
```

W
wizardforcel 已提交
209
如你所见,该程序/ Ruby 解释器吐出错误的输出如下所示:
W
init  
wizardforcel 已提交
210

W
wizardforcel 已提交
211
```rb
W
init  
wizardforcel 已提交
212 213 214 215 216 217 218
thread_variables_1.rb:13: undefined local variable or method `thread_variable' for main:Object (NameError)
```

它说有一个名为`thread_variable`的未定义局部变量或方法。 这意味着主线程中的语句无法访问线程`a`中声明的变量。 因此,从前两个示例可以清楚地看出,线程可以访问主线程中声明的变量,而线程的作用域中声明的变量不能被主作用域中的语句访问。

### 21.2。 线程排除

W
wizardforcel 已提交
219
假设有两个共享同一资源的线程,则将该资源作为变量。 假设第一个线程修改了变量,而第二个线程尝试访问变量时,会发生什么呢? 答案很简单明了,尽管程序运行似乎没有错误,但你可能无法获得理想的结果。 这个概念很难理解,让我尝试用一​​个例子来解释它。 输入并执行 [thread_exclusion.rb](code/thread_exclusion.rb)
W
init  
wizardforcel 已提交
220

W
wizardforcel 已提交
221
```rb
W
init  
wizardforcel 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
#!/usr/bin/ruby
# thread_exclusion.rb

x = y = 0
diff = 0
Thread.new {
  loop do
    x+=1
    y+=1
  end
}
Thread.new {
  loop do
    diff += (x-y).abs
  end
}
sleep(1) # Here main thread is put to sleep
puts "difference = #{diff}"
```

Output

W
wizardforcel 已提交
244
```rb
W
init  
wizardforcel 已提交
245 246 247 248 249 250 251
difference = 127524
```

仔细阅读程序。 在开始任何线程之前,我们将三个变量`x``y`和 diff 分配给值 0。然后在第一个线程中,我们开始一个循环,在该循环中我们增加`x``y`的值。 在另一个线程中,我们找到`x``y`之间的区别,并将其保存在名为`diff`的变量中。 在第一个线程中`x``y`同时增加,因此语句`diff += (x-y).abs`不应对变量`diff`添加任何内容,因为`x``y`始终相等,并且它们的差值始终为零,因此 它们的差的绝对值也将始终为零。

在此程序中,我们不等待线程加入(因为它们包含无限循环),我们使用命令`sleep(1)`使主循环休眠一秒钟,然后在以下语句中打印 diff 的值

W
wizardforcel 已提交
252
```rb
W
init  
wizardforcel 已提交
253 254 255
puts "difference = #{diff}"
```

W
wizardforcel 已提交
256
人们会期望该值为零,但我们得到的值为 127524,在你的计算机中,该值可能会有所不同,因为它取决于机器速度,运行的处理器和其他参数。 但是道德是`diff`应该为零有一定价值,为什么呢?
W
init  
wizardforcel 已提交
257 258 259

我们在第一个循环中看到`x`递增,然后`y`递增,假设`x`的值为 5,y 值为 4,即`x`刚在语句中递增 `x += 1`,现在 Ruby 解释器将要读取并执行`y += 1`,这将使`y`从 4 变为 5。在此阶段,第二个线程由计算机执行。 所以在声明中

W
wizardforcel 已提交
260
```rb
W
init  
wizardforcel 已提交
261 262 263 264 265 266 267
diff += (x-y).abs
```

`x`设置为 5,将`y`设置为 4 将意味着`diff`将递增 1。以类似的方式,当主循环休眠一秒钟时,我们创建的两个线程将完成数千个循环周期, 因此`diff`的值将显着增加。 这就是为什么我们将 diff 的值当作一个大数字来获取的原因。

好了,我们已经看到了如何不以错误的方式编写程序,现在让我们看看如何以正确的方式编写程序。 现在,我们的任务是同步已创建的两个线程,以便当另一个线程处于某个繁忙进程的中间时,一个线程不会访问其他线程资源。 为此,我们将使用互斥体,即互斥。 在文本编辑器中键入以下程序 [thread_exclusion_1.rb](code/thread_exclusion_1.rb) 并执行它

W
wizardforcel 已提交
268
```rb
W
init  
wizardforcel 已提交
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
#!/usr/bin/ruby
# thread_exclusion_1.rb

require 'thread'

mutex = Mutex.new
x = y = 0
diff = 0
Thread.new {
  loop do
    mutex.synchronize do
      x+=1
      y+=1
    end
  end
}
Thread.new {
  loop do
    mutex.synchronize do
      diff += (x-y).abs
    end
  end
}
sleep(1) # Here main thread is put to sleep
puts "difference = #{diff}"
```

Output

W
wizardforcel 已提交
298
```rb
W
init  
wizardforcel 已提交
299 300 301 302 303
difference = 0
```

如上所示,我们将输出显示为`difference = 0`,这意味着`diff`变量为零。 某些原因阻止了第二个线程在第一个线程繁忙时访问第一个线程中的`x``y`。 这两个线程如何学会正确地共享资源。 仔细研究代码,我们看到我们通过键入包含了一个线程包

W
wizardforcel 已提交
304
```rb
W
init  
wizardforcel 已提交
305 306 307 308 309
require 'thread'
```

接下来,我们使用以下命令创建了一个名为 Mutex 的 Mutex 变量。

W
wizardforcel 已提交
310
```rb
W
init  
wizardforcel 已提交
311 312 313 314 315
mutex = Mutex.new
```

那么代码就和往常一样,除了线程内部。 让我们看一下第一个线程

W
wizardforcel 已提交
316
```rb
W
init  
wizardforcel 已提交
317 318 319 320 321 322 323 324 325 326 327 328
Thread.new {
  loop do
    mutex.synchronize do
      x += 1
      y += 1
    end
  end
}
```

我们看到`x += 1``y += 1`语句包含在`mutex.synchronize`块中。 同样,计算`x``y`之差的代码也包含在`mutex.synchronize`块中,如下所示:

W
wizardforcel 已提交
329
```rb
W
init  
wizardforcel 已提交
330 331 332 333 334 335 336 337 338 339 340 341 342
Thread.new {
  loop do
    mutex.synchronize do
      diff += (x-y).abs
    end
  end
}
```

通过这样做,我们告诉计算机这两个线程共享一个公共资源,并且只有另一个线程释放该资源时,一个线程才能访问该资源。 这样,当在`diff += (x-y).abs`中计算出差(`diff`)时,`x``y`的值将始终相等,因此 diff 永远不会增加并且永远保持为零。

### 21.3。 死锁

W
wizardforcel 已提交
343
你是否曾经站在队列中,或等待过什么。 我们都在机场等了一个地方,以便对我们的行李进行扫描和清理。 可以说行李扫描机出故障了,你被困在机场。 预计你将参加重要的公司会议,并且有重要的演讲,并且你必须发表重要的讲话。 由于行李扫描仪发生故障,因此你所需要的资源不可用,并且你掌握了在会议中进行交谈的关键知识,因此会议忙得不可开交。 会议中的一个可能会答应带他的家人去看电影,他可能会在延迟的会议之后回来很晚,因此一切都搞砸了。
W
init  
wizardforcel 已提交
344

W
wizardforcel 已提交
345
想象一下,行李扫描仪,你,一个必须听见你的会议的人,是 Ruby 程序的线程。 由于行李扫描仪不会释放资源(你的行李),因此你的流程将被延迟,并且由于延迟,许多其他依赖于你的流程也会延迟。 这种情况称为死锁。 它也发生在 Ruby 程序中。 每当出现这种情况时,人们就会等待而不是向前冲。 你等待行李被放行,公司等待你的到来,芒家族等待他将其带到电影中。 如果不等人们赶时间,就会造成混乱。 Ruby 具有避免这种混乱并处理这种死锁的机制。 我们使用一种称为条件变量的东西。 看下面的程序( [thread_condition_variable.rb](code/thread_condition_variable.rb) ),键入并执行它。
W
init  
wizardforcel 已提交
346

W
wizardforcel 已提交
347
```rb
W
init  
wizardforcel 已提交
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
#!/usr/bin/ruby
# thread_condition_variable.rb

require 'thread'
mutex = Mutex.new

c = ConditionVariable.new
a = Thread.new {
  mutex.synchronize {
    puts "Thread a now waits for signal from thread b"
    c.wait(mutex)
    puts "a now has the power to use the resource"
  }
}

puts "(Now in thread b....)"

b = Thread.new {
  mutex.synchronize {
    puts "Thread b is using a resource needed by a, once its done it will signal to a"
    sleep(4)
    c.signal
    puts "b Signaled to a to acquire resource"
  }
}
a.join
b.join
```

Output

W
wizardforcel 已提交
379
```rb
W
init  
wizardforcel 已提交
380 381 382 383 384 385 386 387 388
Thread a now waits for signal from thread b
(Now in thread b....)
Thread b is using a resource needed by a, once its done it will signal to a
b Signaled to a to acquire resource
a now has the power to use the resource
```

仔细研究程序并输出。 查看输出。 首先,执行线程`a`中的语句,并打印出线程`a`正在等待线程`b`发出信号以继续执行该语句。 参见线程`a`,我们编写了代码`c.wait(mutex)`,其中`c`是代码中声明的条件变量,如下所示:

W
wizardforcel 已提交
389
```rb
W
init  
wizardforcel 已提交
390 391 392 393 394
c = ConditionVariable.new
```

所以现在线程 a 等待,现在在线程`b 中遇到以下行时,执行集中在线程 b 上

W
wizardforcel 已提交
395
```rb
W
init  
wizardforcel 已提交
396 397 398 399 400 401 402 403 404
puts "Thread b is using a resource needed by a, once its done it will signal to a"
```

它打印出线程`b`正在使用`a`所需的某些资源,下一个线程`b`休眠 4 秒,因为我们在其中指定了`sleep(4)`。 可以避免使用`sleep`语句,我给出了`sleep`,因为在读者执行程序时,它使他等待并感到条件变量的工作原理。

然后,在 4 秒钟后,线程`b`向线程`a`发出信号,其等待可以使用语句`c.signal`结束。 现在,线程 a 接收信号并可以执行其其余语句,即在`c.signal`之后,线程`a`和线程`b`可以同时执行。

### 21.4。 创建多个线程

W
wizardforcel 已提交
405
假设你有一种情况,必须创建许多线程,并且必须以优雅的方式完成,说一种情况可能会出现,你甚至不知道可以创建多少个线程,但是必须创建它们并且 程序应该等待他们加入然后退出,所以让我们看看如何编写代码。
W
init  
wizardforcel 已提交
406

W
wizardforcel 已提交
407
因此,在你的文本编辑器中键入以下程序并运行它
W
init  
wizardforcel 已提交
408

W
wizardforcel 已提交
409
```rb
W
init  
wizardforcel 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
# many_threads.rb

def launch_thread string
  thread = Thread.new do
    3.times do
      puts string
      sleep rand(3)
    end
  end
  return thread
end

threads = []
threads << launch_thread("Hi")
threads << launch_thread("Hello")

threads.each {|t| t.join}
```

Output

W
wizardforcel 已提交
431
```rb
W
init  
wizardforcel 已提交
432 433 434 435 436 437 438 439
Hi
Hello
Hello
Hello
Hi
Hi
```

W
wizardforcel 已提交
440
如果你没有获得如上所述的确切输出,请不要担心,因为程序中存在一些随机性。 让我解释一下该程序,以便你看清楚它。
W
init  
wizardforcel 已提交
441 442 443

首先,我们声明一个将容纳线程的数组,如下所示

W
wizardforcel 已提交
444
```rb
W
init  
wizardforcel 已提交
445 446 447 448 449
threads = []
```

接下来,我们将使用函数`launch_thread`将值放入线程数组,如下所示

W
wizardforcel 已提交
450
```rb
W
init  
wizardforcel 已提交
451 452 453 454 455
threads << launch_thread("Hi")
```

让我们分析`launch_thread`函数中发生的事情,首先我们有一个如下所示的函数

W
wizardforcel 已提交
456
```rb
W
init  
wizardforcel 已提交
457 458 459 460 461 462 463
def launch_thread string
  …....
end
```

我们从函数返回一个名为`thread`的变量

W
wizardforcel 已提交
464
```rb
W
init  
wizardforcel 已提交
465 466 467 468 469 470 471
def launch_thread string
  return thread
end
```

我们将新创建的线程分配给变量线程

W
wizardforcel 已提交
472
```rb
W
init  
wizardforcel 已提交
473 474 475 476 477 478 479 480 481
def launch_thread string
  thread = Thread.new do
  end
  return thread
end
```

我们在新创建的线程中放入一些代码

W
wizardforcel 已提交
482
```rb
W
init  
wizardforcel 已提交
483 484 485 486 487 488 489 490 491 492 493 494 495
def launch_thread string
  thread = Thread.new do
    3.times do
      puts string
      sleep rand(3)
    end
  end
  return thread
end
```

而已。 简而言之,我们创建线程,在其中运行代码,然后将其返回并存储在`threads`数组中。 这行也发生了同样的事情

W
wizardforcel 已提交
496
```rb
W
init  
wizardforcel 已提交
497 498 499 500 501
threads << launch_thread("Hello")
```

现在,我们必须等待每个线程加入主程序(或主线程)。 因此,我们编写了如下所示的连接代码

W
wizardforcel 已提交
502
```rb
W
init  
wizardforcel 已提交
503 504 505 506 507 508 509 510 511
threads.each {|t| t.join}
```

这将等待所有线程完成并与主代码联接。

因此,我们编写了一个程序,该程序可以创建所需的任意数量的线程(在上面的示例中为两个),所有线程将被收集到一个数组中,主程序将等待所有线程完成并与之连接,然后 将退出。

现在看看 [many_threads_1.rb](code/many_threads_1.rb) 和 [many_threads_2.rb](code/many_threads_2.rb) ,执行它们并向自己解释它们如何工作。 最好为他们写一个好的解释并邮寄给我,以便我可以将其放在这本书中。

W
wizardforcel 已提交
512
```rb
W
init  
wizardforcel 已提交
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
# many_threads_1.rb

def launch_thread string
  thread = Thread.new do
    3.times do
      puts string
      sleep rand(3)
    end
  end
  return thread
end

threads = []

4.times do |i|
  threads << launch_thread("Thread #{i}")
end

threads.each {|t| t.join}
```

W
wizardforcel 已提交
534
```rb
W
init  
wizardforcel 已提交
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
# many_threads_2.rb

def launch_thread string
  thread = Thread.new do
    3.times do
      puts string
      sleep rand(3)
    end
  end
  return thread
end

threads = []

puts "How many threads should run?"
count = gets.to_i

count.times do |i|
  threads << launch_thread("Thread #{i}")
end

threads.each {|t| t.join}
```

### 21.5。 线程异常

每当程序运行时发生异常,并且如果异常处理不当,程序就会终止。 让我们看看线程中有异常时会发生什么。 在文本编辑器中键入代码 [thread_exception_true.rb](code/thread_exception_true.rb) 并执行它。

W
wizardforcel 已提交
563
```rb
W
init  
wizardforcel 已提交
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
#!/usr/bin/ruby
# thread_exception_true.rb

t = Thread.new do
  i = 5
  while i >= -1
    sleep(1)
    puts 25 / i
    i -= 1
  end
end

t.abort_on_exception = true
sleep(10)
puts "Program completed"
```

Output

W
wizardforcel 已提交
583
```rb
W
init  
wizardforcel 已提交
584 585 586 587 588 589 590 591 592 593 594 595
5
6
8
12
25
thread_exception_true.rb:8:in `/': divided by 0 (ZeroDivisionError)
	from thread_exception_true.rb:8
	from thread_exception_true.rb:4:in `initialize'
	from thread_exception_true.rb:4:in `new'
	from thread_exception_true.rb:4
```

W
wizardforcel 已提交
596
注意,在程序中我们创建了一个名为`t`的线程,如果你很安静,则程序中没有`t.join`。 与其等待线程加入,不如等待足够长的时间来完成线程。 为了使线程完成,我们使用语句`sleep(10)`等待 10 秒。
W
init  
wizardforcel 已提交
597 598 599

注意我们设置的`t.abort_on_exception = true`行,如果线程`t`中引发异常,则程序必须中止。 现在让我们分析线程`t`中的内容。 线程`t`包含以下代码

W
wizardforcel 已提交
600
```rb
W
init  
wizardforcel 已提交
601 602 603 604 605 606 607 608 609 610 611 612
t = Thread.new do
  i = 5
  while i >= -1
    sleep(1)
    puts 25 / i
    i -= 1
  end
end
```

注意,我们将 25 除以`i`,然后得出除法结果。 `i`在每次循环迭代中递减 1,因此,当`i`变为零且被其除以 25 时,它将引发异常。 因此,在循环的第六次迭代中,将 25 除以零,引发异常,并通过吐出以下内容停止程序

W
wizardforcel 已提交
613
```rb
W
init  
wizardforcel 已提交
614 615 616 617 618 619 620 621 622
thread_exception_true.rb:8:in `/': divided by 0 (ZeroDivisionError)
	from thread_exception_true.rb:8
	from thread_exception_true.rb:4:in `initialize'
	from thread_exception_true.rb:4:in `new'
	from thread_exception_true.rb:4
```

发生这种情况是因为我们已将`t.abort_on_exception`设置为`true`(请参见突出显示的代码)。 如果将其设置为`false`会发生什么。 看一下程序 [thread_exception_false.rb](code/thread_exception_false.rb) 。 在程序中,我们将`t.abort_on_exception`设置为`false`。 在文本编辑器中键入程序并运行

W
wizardforcel 已提交
623
```rb
W
init  
wizardforcel 已提交
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
#!/usr/bin/ruby
# thread_exception_false.rb

t = Thread.new do
  i = 5
  while i >= -1
    sleep(1)
    puts 25 / i
    i -= 1
  end
end

t.abort_on_exception = false
sleep(10)
puts "Program completed"
```

看一下输出

W
wizardforcel 已提交
643
```rb
W
init  
wizardforcel 已提交
644 645 646 647 648 649 650 651 652 653
5
6
8
12
25
Program completed
```

从输出中可以看到,没有异常发生的痕迹 4。 执行线程`t`之后的代码,我们得到显示为`Program Completed`的输出。 这是因为我们将`abort_on_exception`设置为`false`。

W
wizardforcel 已提交
654
你可以从最后两个程序中看到我们尚未使用`t.join`,相反,我们已经等待了足够长的时间以终止线程。 之所以这样,是因为一旦我们将线程(导致)与父线程(在这种情况下为主线程)连接在一起,子线程中出现的异常就会传播到父线程/等待线程,因此`abort_on_exception`甚至没有作用 设置为 false。 因此,每当异常发生时,它就会反映在我们的终端上。
W
init  
wizardforcel 已提交
655 656 657

### 21.6。 线程类方法

W
wizardforcel 已提交
658
你可以使用某些线程方法来操纵线程的属性。 这些在下面列出。 如果你一点都不懂,那就不用担心。
W
init  
wizardforcel 已提交
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699

<colgroup><col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"></colgroup> 
| 斯诺 | 方法 | 它能做什么 |
| --- | --- | --- |
| 1。 | Thread.abort_on_exception | 返回异常情况下全局中止的状态。 默认值为 false。 设置为 true 时,如果任何线程中引发异常,将导致所有线程中止(进程将退出(0))。 |
| 2。 | Thread.abort_on_exception = | 设置为 true 时,如果引发异常,则所有线程将中止。 返回新状态。 |
| 3。 | 线程临界 | 返回全局线程严重条件的状态。 |
| 4。 | Thread.critical = | 设置全局线程紧急条件的状态并返回。 设置为 true 时,禁止调度任何现有线程。 不会阻止新线程的创建和运行。 某些线程操作(例如停止或杀死线程,在当前线程中休眠以及引发异常)可能导致即使在关键部分中也要调度线程。 |
| 5, | 线程电流 | 返回当前正在执行的线程。 |
| 6。 | 线程退出 | 终止当前正在运行的线程并安排另一个线程运行。 如果此线程已被标记为已终止,则 exit 返回该线程。 如果这是主线程或最后一个线程,请退出进程。 |
| 7 | Thread.fork {块} | Thread.new 的同义词 |
| 8。 | Thread.kill(aThread) | 使给定的 aThread 退出 |
| 9。 | 线程列表 | 返回所有可运行或已停止线程的 Thread 对象数组。 线。 |
| 10。 | 线程主 | 返回该进程的主线程。 |
| 11。 | Thread.new([arg] *){&#124; args &#124; 阻止 | 创建一个新线程以执行块中给出的指令,然后开始运行它。 传递给 Thread.new 的所有参数都传递给该块。 |
| 12 | 线程传递 | 调用线程调度程序以将执行传递给另一个线程。 |
| 13 | Thread.start([args] *){&#124; args &#124; 阻止 | 基本上与 Thread.new 相同。 但是,如果 Thread 类是子类,则在该子类中调用 start 不会调用该子类的 initialize 方法。 |
| 14。 | 线程停止 | 停止当前线程的执行,使其进入睡眠状态,并计划另一个线程的执行。 将关键条件重置为 false |

### 21.7。 线程实例方法

由于 Ruby 中的所有内容都是对象,因此 ia 线程也是对象。 与许多对象一样,线程具有可以调用以访问或设置线程中的属性的函数或方法。 下面列出了一些功能及其用途(thr 是 Thread 类的实例变量):

<colgroup><col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"></colgroup> 
| Sno. | Method | What it does |
| --- | --- | --- |
| 1 个 | 活着吗? | 如果线程处于活动状态或睡眠状态,则此方法返回 true。 如果线程已终止,则返回 false。 |
| 2 | 退出 | 杀死或退出线程 |
| 3 | thr.join | 该进程等待线程与创建子线程的进程或线程联接。 子线程完成执行后,主线程在 thr.join 之后执行语句 |
| 4 | 杀伤力 | 相同的广告退出次数 |
| 5 | 优先级 | 获取线程的优先级。 |
| 6 | thr.priority = | 设置线程的优先级。 优先级越高,具有较高编号的线程将具有更高的优先级。 |
| 7 | thr.raise(anException) | 从 thr 引发异常。 呼叫者不必是 thr。 |
| 8 | 运行 | 唤醒 thr,使其有资格进行调度。 如果不在关键部分,则调用调度程序。 |
| 10 | 唤醒 | 将 thr 标记为可进行调度,但是它仍可能在 I / O 上处于阻塞状态。 |
| 11 | 状态 | 返回 thr 的状态:如果 thr 正在睡眠或正在等待 I / O,则进入 sleep;如果 thr 正在执行,则运行;如果 thr 正常终止,则返回 false;如果 thr 终止,则返回 nil。 |
| 12 | 停止吗? | 等待 thr 通过 Thread.join 完成并返回其值。 |
| 13 | thr [aSymbol] | 属性参考-使用符号或 aSymbol 名称返回线程局部变量的值。 如果指定的变量不存在,则返回 nil。 |
| 14 | thr [aSymbol] = | 属性分配-使用符号或字符串设置或创建线程局部变量的值。 |
| 15 | thr.abort_on_exception | 返回 thr 的异常情况中止的状态。 默认值为 false。 |
| 16 | thr.abort_on_exception = | 设置为 true 时,如果 thr 中引发异常,将导致所有线程(包括主程序)中止。 该过程将有效退出(0)。 |