4 releases
0.1.4 | Mar 30, 2023 |
---|---|
0.1.3 | Aug 21, 2021 |
0.1.1 | Jun 15, 2021 |
0.1.0 | Jun 15, 2021 |
#139 in #unsafe
41KB
738 lines
Deprecated
It was probably a bad idea to use this crate in the first place.
breadthread
breadthread
provides a thread for getting that bread.
Certain APIs are thread unsafe, and it would be nice to be able to use them in a thread-safe
context, since certain runtimes require data to be Send
. breadthread
provides
a mechanism to export this work to another thread.
License
MIT/Apache2 License
lib.rs
:
A runtime that allows thread-unsafe code to be executed on a designated
Motivation
There are quite a few APIs that are thread unsafe. The one I had in mind
while designing this crate is the Windows winuser
windowing API, but
there are many others. While small programs may be able to get away with
being thread unsafe, larger programs, with runtimes that require Safe
bounds, may not.
Ordinarily, these programs will have to resort to convoluted systems to ensure that code runs on a desigated "local" thread or thread pool. The goal of this crate is to simplify these systems by providing a runtime that allows thread-unsafe code to be executed on a designated thread.
Usage
First, create a type to use as a [Tag
]. This type will be used to
uniquely identify the thread that the runtime will run on at compile time.
This ensures that values that are native to one thread will not be used
on another thread.
Any 'static
type can be used as a tag, and it's recommended to use a
zero-sized type.
struct MyTag;
Then, create a BreadThread
type. This is the runtime that directives
are sent along. To spawn a new thread to run directives on, use the new
method.
use breadthread::BreadThread;
let bt = BreadThread::<'static, MyTag>::new();
However, if you already have a system that you'd like to take advantage of
(a dedicated thread pool like rayon
, for instance), you can use the
undriven()
method to create both a BreadThread
and a Driver
. You can
transform a thread or thread-like task into the driving thread by calling
drive()
on the Driver
.
let (bt, driver) = BreadThread::<'static, MyTag>::undriven();
my_runtime::spawn_task(move || driver.drive());
Note that the BreadThread
and Driver
are parameterized by a lifetime, which
in this case is 'static
. The lifetime is used as a bound for the directives that
we send to the thread. Consider using this if you want to send a directive that borrows
other data.
Now, we can call the run
method on the BreadThread
to run a given method.
use breadthread::DirectiveOutput;
let input_value = 7;
let value = bt.run((), move |()| {
let ret_ty = thread_unsafe_code(input_value);
DirectiveOutput {
thread_safe_value: (),
thread_unsafe_value: ret_ty,
deleted_values: vec![],
}
});
bt.run()
expects a return type of DirectiveOutput
, which consists of:
- A
thread_safe_value
that is implied to beSend
andSync
. - A
thread_unsafe_value
that may not be any of these. Oncevalue
returns, this value will be wrapped inObject
, which essentially allows it to be sent to other threads, but only used in the driving thread. To use this value again, pass it into thebt.run()
method in place of the empty tuple, and it can be used raw again. Tuples and slices ofObject
s can be returned using this strategy. Note that values of this kind have to implementCompatible
. deleted_values
consists of values to be deleted from the thread's internal bank that keeps track of the values that are valid for it. By default, values returned are added to the "valid" list, so if you don't expect to use the value again, you can add it to thedeleted_values
list.
value
is of type Value
, and resolves to a tuple of thread_safe_value
and the
safe version of thread_unsafe_value
. It can be resolved in one of three ways:
- Poll for whether or not it's resolved using
value.resolve()
. - Wait for it to be resolved by parking the thread, using
value.wait()
. - With the
async
feature enabled,Value
implementsFuture
.
Safety
Using tag
ensures that the bread thread will only have values that are tagged as
valid associated with it. As long as two threads do not have the same tag, this
validation is done at compile time, and the only real overhead is in sending and
receiving values from the two threads.
If more than one thread is created with the same tag, by default the library panics.
If this behavior is not desired, enable the fallback
feature. Instead, when two
threads share a tag, they will manually keep track of which values are valid for
which thread.
no_std
The std
feature is enabled by default. Without std
, this crate only relies on the
alloc
crate. However, certain changes are made both externally and internally.
BreadThread::new()
,Driv(er::drive()
andValue::wait()
are not present.- Internally, the data structures use spinlock-based APIs instead of ones based on system synchronization. This is often undesireable behavior.
It is recommended to use the std
feature unless it is necessary to use this crate
in a no_std
environment.
Dependencies
~1–28MB
~353K SLoC