Page 54
另一个奇怪的问题是:我们使用了_left和_right来命名.下划线是怎么回事?我们并没有计划在加锁时使用这两个变量.我们仅仅只想要得到它.因此,Rust会警告我们并没有使用这些值.通过使用下划线,我们告诉Rust这就是我们想要的,它就不会抛出警告了.
如何释放锁呢?_left和_right离开自己的作用域之后就会自动释放.
let table = Arc::new(Table { forks: vec![
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
]});
接下来,在main函数中,我们使用了一个新的Table,然后用Arc<T>来包装它."arc"的意思是"原子引用计数(atomic reference count)",我们需要将Table在多线程间共享.当我们共享的时候,它的引用计数机会增加,然后当线程结束的时候,引用计数就会减少.
let philosophers = vec![
Philosopher::new("Baruch Spinoza",0,1),
Philosopher::new("Gilles Deleuze",1,2),
Philosopher::new("Karl Marx",2,3),
Philosopher::new("Friedrich Nietzsche",3,4),
Philosopher::new("Michel Foucault",0,4),
];
我们需要在Philosophers的构造函数中传入left和right参数.但是这里还有一个细节,并且很重要.如果你看看构造函数的形式,你会发现直到结尾之前都很对称.Michel Foucault(原文误写作为Monsieur Foucault)本应该使用4,0这样的入参,但是,却使用了0,4这样的入参.这是为了防止死锁的发生.事实上,其中一个哲学家是左撇子!这就是一种解决哲学家问题的办法,而且在我看来是最简单的.
let handles: Vec<_> = philosophers.into_iter().map(|p| {
let table = table.clone();
thread::spawn(move || {
p.eat(&table);
})
}).collect();
最后,在map()/collect()的循环中,我们调用了table.clone()方法.作用在Arc<T>对象上的clone()方法会增加它的引用计数,然后当它离开自己的作用域的时候它会减少引用计数.你会发现我们引入了一个新的绑定table,它会影射原来的那个table.这种技巧经常使用,使你可以不必使用两个不同的变量名.
Page 55
到这里,我们的程序可以正常工作了!同一时刻只有两个哲学家可以就餐,所以你会得到如下输出:
恭喜你!你已经使用Rust语言完成了一个经典的并发问题.