在Programming Rust的第465页上,您可以找到代码和说明(我加了强调)
use std::sync::Arc; fn process_files_in_parallel(filenames: Vec<String>, glossary: Arc<GigabyteMap>) -> io::Result<()> { ... for worklist in worklists { // This call to .clone() only clones the Arc and bumps the // reference count. It does not clone the GigabyteMap. let glossary_for_child = glossary.clone(); thread_handles.push( spawn(move || process_files(worklist, &glossary_for_child)) ); } ... }
我们更改了词汇表的类型:要并行运行分析,调用者必须传递
Arc<GigabyteMap>
(指向已移入堆中的GigabyteMap
的智能指针),方法是执行{ {1}}。当我们调用lossary.clone()时,我们正在复制Arc::new(giga_map)
智能指针,而不是整个Arc
。这相当于增加参考计数。进行此更改后,程序将编译并运行,因为它不再依赖于参考生存期。 只要任何线程拥有GigabyteMap
,即使父线程提早解救,它也将使映射保持活动状态。不会有任何数据争用,因为Arc<GigabyteMap>
中的数据是不可变的。
在下一部分中,他们显示了用人造丝重写的内容,
Arc
您可以在改写为使用Rayon的部分中看到,它接受extern crate rayon;
use rayon::prelude::*;
fn process_files_in_parallel(filenames: Vec<String>, glossary: &GigabyteMap)
-> io::Result<()>
{
filenames.par_iter()
.map(|filename| process_file(filename, glossary))
.reduce_with(|r1, r2| {
if r1.is_err() { r1 } else { r2 }
})
.unwrap_or(Ok(()))
}
而不是&GigabyteMap
。他们没有解释这是如何工作的。人造丝为什么不要求Arc<GigabyteMap>
?人造丝如何摆脱直接引用?
答案 0 :(得分:2)
与第一个代码示例中假设的indirect enum Items: Codable {
/**
Initialises an `Items` instance by decoding from the given `decoder`.
- Parameter decoder: The decoder to read data from.
*/
init(from decoder: Decoder) throws {
var itemsSet: Set<Items> = try decodeItems(from: decoder)
if itemsSet.count == 1 {
self = ItemsSet.popFirst()!
} else {
self = .allOfItems(itemsSet)
}
}
/**
Encodes an `Items` instance`.
- Parameter encoder: The encoder to encode data to.
*/
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
switch self {
case .item(let item):
try container.encode(item)
case .allOfItems(let items):
try container.encode(contentsOf: items)
case .anyOfItems(let items):
try container.encode(AnyOfItems(Items.anyOfItems(items)))
}
}
case item(NameVersion)
case anyOfItems(Set<Items>)
case allOfItems(Set<Items>)
}
struct AnyOfItems: Codable {
/**
Initialises an `Items` instance by decoding from the given `decoder`.
- Parameter decoder: The decoder to read data from.
*/
init(from decoder: Decoder) throws {
var itemsSet: Set<Items> = try decodeItems(from: decoder)
if itemsSet.count == 1 {
items = itemsSet.popFirst()!
} else {
items = Items.anyOfItems(itemsSet)
}
}
/**
Encodes an `Items` instance`.
- Parameter encoder: The encoder to encode data to.
*/
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(items, forKey: .items)
}
/**
A memberwise initialiser.
*/
init(_ items: Items) {
self.items = items
}
let items: Items
private enum CodingKeys: String, CodingKey {
case items = "any_of"
}
}
func decodeItems(from decoder: Decoder) throws -> Set<Items> {
var itemsSet: Set<Items> = []
var unkeyedValues = try decoder.unkeyedContainer()
while unkeyedValues.count! > unkeyedValues.currentIndex {
let containerIndexBeforeLoop = unkeyedValues.currentIndex
if let nameVersion = try? unkeyedValues.decode(NameVersion.self) {
itemsSet.insert(Items.item(nameVersion))
} else if let anyOfItems = try? unkeyedValues.decode(AnyOfItems.self) {
itemsSet.insert(anyOfItems.items)
} else if let items = try? unkeyedValues.decode(Items.self) {
itemsSet.insert(items)
}
if unkeyedValues.currentIndex <= containerIndexBeforeLoop { break }
}
return itemsSet
}
不同,Rayon可以保证迭代器不会超过当前的堆栈帧。具体来说,引擎盖下的thread::spawn
使用类似于Rayon的scope
函数的功能,该函数允许生成一个“附加”到堆栈上的工作单元,并在堆栈结束之前加入。
由于Rayon可以(从用户的角度出发,保证通过生命周期限制)任务/线程在调用par_iter
的函数退出之前已加入,因此它可以提供比标准库更符合人体工程学的API。 par_iter
。
人造丝在scope
function's documentation中对此进行了扩展。