面试题答案
一键面试线程在实际项目中的常见应用场景
- I/O 操作:当程序需要进行大量的文件读取、网络请求等 I/O 操作时,使用线程可以在等待 I/O 完成的同时执行其他任务,提高程序的整体效率。例如,一个网络爬虫程序可以使用多个线程同时下载不同的网页。
require 'net/http'
threads = []
urls = ['http://example.com', 'http://ruby-lang.org', 'http://github.com']
urls.each do |url|
threads << Thread.new do
uri = URI(url)
response = Net::HTTP.get(uri)
puts "Fetched #{url}, response length: #{response.length}"
end
end
threads.each(&:join)
- 任务队列处理:将一些耗时任务放入队列,通过线程来并发处理队列中的任务。比如在一个邮件发送系统中,将邮件发送任务放入队列,多个线程从队列中取出任务并发送邮件,提高邮件发送的效率。
require 'thread'
task_queue = Queue.new
threads = []
3.times do
threads << Thread.new do
loop do
task = task_queue.deq
puts "Processing task: #{task}"
# 模拟任务处理
sleep 1
end
end
end
10.times do |i|
task_queue << "Task #{i}"
end
sleep 2
threads.each(&:kill)
- 实时数据处理:在实时应用中,如实时监控系统,一个线程可以负责持续读取传感器数据,而其他线程可以对读取到的数据进行分析和展示,确保系统能够实时响应数据变化。
使用线程时需要注意的问题
- 资源竞争:多个线程同时访问共享资源(如全局变量、文件等)时,可能会导致数据不一致。例如多个线程同时对一个全局计数器进行加一操作,可能会出现数据丢失。
counter = 0
threads = []
10.times do
threads << Thread.new do
1000.times do
counter += 1
end
end
end
threads.each(&:join)
puts counter # 结果可能小于 10000,因为资源竞争
解决办法是使用互斥锁(Mutex)来保护共享资源。
counter = 0
mutex = Mutex.new
threads = []
10.times do
threads << Thread.new do
1000.times do
mutex.synchronize do
counter += 1
end
end
end
end
threads.each(&:join)
puts counter # 结果会是 10000
- 死锁:当两个或多个线程相互等待对方释放资源时,就会发生死锁。例如线程 A 持有资源 X 并等待资源 Y,而线程 B 持有资源 Y 并等待资源 X。
mutex1 = Mutex.new
mutex2 = Mutex.new
thread1 = Thread.new do
mutex1.lock
sleep(1)
mutex2.lock
puts "Thread 1 got both locks"
mutex2.unlock
mutex1.unlock
end
thread2 = Thread.new do
mutex2.lock
sleep(1)
mutex1.lock
puts "Thread 2 got both locks"
mutex1.unlock
mutex2.unlock
end
thread1.join
thread2.join
# 这里可能会发生死锁
避免死锁的方法包括按顺序获取锁、使用超时机制等。 3. 线程安全库:在使用第三方库时,要确保库是线程安全的。如果库不是线程安全的,在多线程环境下使用可能会导致未定义行为。在使用前查阅库的文档确认其线程安全性,或者考虑使用线程安全的替代库。 4. 性能开销:虽然线程可以提高并发性能,但创建和管理线程本身也有开销。过多的线程可能会导致上下文切换频繁,消耗大量系统资源,降低程序性能。需要根据实际情况合理控制线程数量,找到性能平衡点。