线程池控制线程优先级控制

    科技2026-08-23  3

    线程池控制线程优先级控制

    I was reading about the PhantomData type and came across the point made that it can be used as a mechanism for controlling lifetimes. From here I started to play around with it a bit and came up with something interesting.

    我正在阅读有关PhantomData类型的信息,并指出它可以用作控制生命周期的机制。 从这里开始,我开始尝试一些有趣的事情。

    The example itself might be a bit awkward, but bare with me :)

    这个例子本身可能有点尴尬,但对我来说却是bare废:)

    场景🗺️ (The Scenario 🗺️)

    In the example below, we have two threads, one reading (Reader) from a file every 100 milliseconds and one writing (Writer) to a file with some pauses in-between writes. The Reader and Writer are independent and have no knowledge of each other.

    在下面的示例中,我们有两个线程,一个线程每100毫秒从文件中读取一次( Reader) ,一个线程向一个文件中写入( Writer ),两次写入之间存在一些暂停。 读者和作家是独立的,彼此之间没有知识。

    When the Reader is dropped we want to stop our thread associated with the Reader. This is done in the Drop implementation.

    删除Reader时,我们要停止与Reader关联的线程。 这是在Drop实施中完成的。

    use std::fs::{read_to_string, write}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use std::time::Duration; pub struct Writer { path: PathBuf, } impl Writer { pub fn new(path: &PathBuf) -> Writer { Writer { path: path.clone() } } fn write(&self, content: String) { println!("Writing: \"{}\"", content); write(&self.path, content).unwrap(); } } pub struct Reader { path: PathBuf, handle: Option<thread::JoinHandle<()>>, keep_reading: Arc<AtomicBool>, } impl Drop for Reader { fn drop(&mut self) { println!("Dropping Reader"); self.keep_reading.store(false, Ordering::Relaxed); if let Some(handle) = self.handle.take() { if let Err(e) = handle.join() { println!("Failed to wait for thread: {:?}", e); } } } } impl Reader { pub fn new(path: &PathBuf) -> Reader { Reader { path: path.clone(), handle: None, keep_reading: Arc::new(AtomicBool::new(true)), } } pub fn start(&mut self) { let keep_reading = self.keep_reading.clone(); let path = self.path.clone(); let handle = thread::spawn(move || loop { thread::sleep(Duration::from_millis(100)); if let Ok(content) = read_to_string(&path) { println!("Content is: {:?}", content); } if !keep_reading.load(Ordering::Relaxed) { break; } }); self.handle = Some(handle) } } fn main() { let path = PathBuf::from("./file-to-write-to.txt"); std::fs::remove_file(&path).unwrap_or_default(); { let mut reader = Reader::new(&path); reader.start(); } let writer = Writer::new(&path); writer.write("First Value".to_string()); thread::sleep(Duration::from_millis(400)); writer.write("Second Value".to_string()); thread::sleep(Duration::from_millis(400)); }

    需要一些改变🚧(Needs some changes 🚧)

    There’s a problem with this code. Since the Reader is being declared in the inner block, the Reader will be dropped before it even has a chance to read any of the writes to the file.

    这段代码有问题。 由于Reader是在内部块中声明的,因此Reader将在甚至有机会读取对该文件的任何写入之前被删除。

    The problem is fairly easy to fix in this example, we just remove the inner block and everything is fine. It doesn’t really fix the problem in the bigger picture though. The code base could be big and complex and an issue like this becomes non-trivial.

    在此示例中,该问题很容易解决,我们只需要删除内部块即可,一切都很好。 但是,从整体上看,它并不能真正解决问题。 代码库可能又大又复杂,这样的问题就变得不那么重要了。

    We want to make sure that the Reader always reads the last write at least once. In other words, we want the Reader to live as long or longer than the Writer.

    我们要确保阅读器始终至少读取一次上一次写入。 换句话说,我们希望Reader的寿命比Writer的寿命长或更长。

    寻找解决方案🕵️‍♀️ (Looking for a solution 🕵️‍♀️)

    So how can we make sure that the Writer never outlives the Reader? One solution is to make the Writer hold a reference to the Reader as a field in the Writer struct. This works, but it forces us to increase the size of the Writer. It also smudges the semantics of the Writer a bit. In our made-up scenario, it doesn’t make sense to store a reference to the Reader.

    那么,如何确保作家永远不会超越读者? 一种解决方案是使Writer持有对Reader的引用作为Writer结构中的字段。 这有效,但是它迫使我们增加Writer的大小。 它还会模糊作家的语义。 在我们的组合方案中,没有必要存储对Reader的引用。

    Enter PhantomData 👻!

    输入PhantomData👻!

    use std::fs::read_to_string; use std::fs::write; use std::marker::PhantomData; use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; use std::thread; use std::time::Duration; pub struct Reader { path: PathBuf, handle: Option<thread::JoinHandle<()>>, keep_reading: Arc<AtomicBool>, } pub struct Writer<'a> { path: PathBuf, _phantom_data: PhantomData<&'a ()>, } impl Writer<'_> { fn write(&self, content: String) { println!("Writing: \"{}\"", content); write(&self.path, content).unwrap(); } } impl Drop for Reader { fn drop(&mut self) { println!("Dropping Reader"); self.keep_reading.store(false, Ordering::Relaxed); if let Some(handle) = self.handle.take() { if let Err(e) = handle.join() { println!("Failed to wait for thread: {:?}", e); } } } } impl Reader { pub fn new(path: PathBuf) -> Reader { Reader { path, handle: None, keep_reading: Arc::new(AtomicBool::new(true)), } } pub fn new_writer(&self) -> Writer { Writer { path: self.path.clone(), _phantom_data: PhantomData, } } pub fn start(&mut self) { let keep_reading = self.keep_reading.clone(); let path = self.path.clone(); let handle = thread::spawn(move || loop { thread::sleep(Duration::from_millis(100)); let content = read_to_string(&path); println!("Content is: {:?}", content); if !keep_reading.load(Ordering::Relaxed) { break; } }); self.handle = Some(handle) } } fn main() { let path = PathBuf::from("./file-to-write-to.txt"); let mut writer = None; { let mut reader = Reader::new(path); reader.start(); writer = Some(reader.new_writer()); } if let Some(writer) = writer { writer.write("First Value".to_string()); thread::sleep(Duration::from_millis(400)); writer.write("Second Value".to_string()); thread::sleep(Duration::from_millis(400)); } }

    这里发生了什么? ‍🔬 (What’s going on here? 👩‍🔬)

    The modified version of the code now has PhantomData in the mix. The Writer struct now contains a field with the type PhantomData, more specifically PhantomData<&’a ()>. What’s interesting here is that the important part is the lifetime parameter and not the type parameter, which is just Rust’s empty type “()”.

    该代码的修改后的版本现在包含PhantomData 。 Writer结构现在包含一个类型为PhantomData的字段,更具体地说是PhantomData <&'a()> 。 这里有趣的是,重要的部分是生命周期参数,而不是类型参数,它只是Rust的空类型“ ()”。

    By declaring the PhantomData field this way, the Writer struct now declares that it can be made dependent on some external lifetime. Since it’s not related to any meaningful type you can think of this field as a mechanism to control how long the Writer should exist. It is not bound to the Reader struct in any way.

    通过以这种方式声明PhantomData字段, Writer结构现在声明可以依赖于某些外部生存期。 由于它与任何有意义的类型都不相关,因此您可以将该字段视为控制Writer应该存在多长时间的机制。 它不以任何方式绑定到Reader结构。

    奖励🎖️ (The Reward 🎖️)

    The second version of the example doesn’t compile and that’s cool. The compiler will complain that the Reader does not live long enough, and that’s exactly what we want. The code declares the Reader in an inner block to simulate a shorter lifetime for the Reader, compared to the declared Writer.

    该示例的第二个版本无法编译,这很酷。 编译器会抱怨Reader寿命不足,而这正是我们想要的。 与声明的Writer相比,该代码在内部块中声明Reader ,以模拟Reader的较短生存期。

    This means that when we try to create a new Writer in the inner block, Rust will realize that the Reader will be dropped at the end of the inner block, but not the Writer. The Writer needs to outlive the inner scope and extend its lifespan until the outer block has ended. If you remove the inner block the code will compile and run the way we intended it to do.

    这意味着当我们尝试在内部块中创建一个新的Writer时,Rust会意识到Reader将被丢弃在内部块的末尾,而不是Writer。 编写器需要延长内部作用域的寿命,并延长其使用寿命,直到外部功能块结束为止。 如果删除内部块,则代码将按照我们预期的方式编译并运行。

    What’s nice about this is that, instead of spending time debugging at runtime for possible thread bugs, you can find some of the bugs during compile-time.

    这样做的好处是,您可以在编译时发现一些错误,而不是花时间在运行时调试可能的线程错误。

    尾注🗒️ (Endnote 🗒️)

    It can be a bit tricky to see all of the “magic” in the code since most of the lifetimes are being inferred by the compiler. I would encourage you to copy the code and mess around with it a bit, just to see what happens when you change things around.

    要查看代码中的所有“魔术”可能有些棘手,因为大多数生命周期都是由编译器推断的。 我鼓励您复制代码并对其进行处理,以了解更改内容时会发生什么。

    /Robert

    /罗伯特

    翻译自: https://medium.com/@fabrlyn/controlling-threads-using-phantomdata-in-rust-3a53eb0c172

    线程池控制线程优先级控制

    Processed: 0.013, SQL: 10