Argentum threads act as ultra-lightweight processes. They don't use mutexes, conditional variables or atomics to share state. They share immutable objects naturally and isolate hierarchies of mutable objects inside single thread. There is a mechanism of asynchronous request passing that allows:
- to move mutable objects between threads
- to execute code on the thread of interest with full access to needed objects
- to pass weak-pointers across threads.
Since all objects of all threads reside in the same address space, all these operations are lightweight. So Argentum provides data isolation and well-formed IPC at the cost comparable to the usual multithreaded processing.
Argentum rules on multithreading
- Each thread has its own hierarchy of mutable objects.
- Mutable objects of one thread are not directly accessible by other threads (like pointer in one OS process has no meaning in other processes).
- Threads do not communicate. Instead objects inside threats communicate by posting to each other asynchronous requests (like IPC).
- Request contains:
- a delegate (that combines a weak pointer to the receiver object and a function to call)
- a tuple of parameters to pass across threads.
- Object in one thread can have delegate referring to object in another threads, but this delegate is opaque:
- If called synchronously, it behaves as not connected to any object.
- But if posted asynchronously, it:
- automatically discovers the right queue to be put into,
- activates on the rights thread,
- locks its target and allows full access to this object and all its hierarchy.
- Object in one thread can also have weak pointer referencing object from another thread. And this pointer is also opaque:
- If dereferenced, it reads as null.
- It can be used to create delegate, that if posted, allows to execute code on the target's thread.
- It also can be posted as a parameter of another request that:
- If delivered to the same thread as target, can be used to access this object
- If delivered to another thread allows to share knowledge about target objects between threads.
- Frozen shared objects can be shared between threads without limitations.
Thread Creation and Maintenance
Main application thread is special. It pre-exists. The application entry point executed on this thread as well as constants' initializers. Runtime library has a special method sys_setMainObject
that installs any object as the main root object of this thread.
Application works as long as this object is not null. This object will represent all application's mutable state.
Somewhere in this application state there may be created and retained multiple sys_Thread
objects. They represent additional threads. Thread lives as long as its sys_Thread
object is alive. Thread object has start
method that accepts an object to become the root in this thread hierarchy. The started thread is ready to accept asynchronous requests to its internal objects.
Thread interop
Argentum has delegates - special type of callable objects that combine method of an object (or an inline code that accessed some object using 'this
') and a weak pointer to the instance.
Delegates are created with two kinds of syntax:
- pinPointerToObject.methodName
- weakOrPinPointerToObject.&someModuleGlobalName(parmeters){code}
Delegates can be called immediately as functions, stored in fields/variables/passed as parameters and results but also they can be posted asynchronously using syntax:
- delegate ~ (parameters)
This action
- evaluates parameter values,
- finds the thread where delegate target located and posts all data to this thread queue
- later in context of the target thread this data will be extracted from this queue
- target object by this weak pointer is locked
- and this code or method is executed with all passed parameters.
If delegate parameters are objects, they are detached from their previous thread and attached to a new one.
using sys { Object, Array, String, Thread, log, setMainObject }
// Main thread state
class App{
// Main thread holds additional worker thread
worker = Thread(Object); // worker thread hosts a generic Object with no internal state
}
// Set state for main thread
app = App;
setMainObject(app);
// Start the worker thread
app.worker.start(Object); // Set an `Object` instance as the root in the worker thread.
// Create a delegate bound to the root object of the worker thread
greetingsDelegate = app.worker.root().&workerCode(){
log("Hello from the worker thread\n");
};
// Post it
greetingsDelegate~();
// Create another delegate bound to the main app object, that stops the application
endAppDelegate = app.&endEverything(){
log("Shutdown from the main thread\n");
setMainObject(?sys_Object); // destroys `app`, `worker` and inner object of the thread
});
// Create and post a delegate for worker thread that calls our main thread delegate
app.worker.root().&workerWithCallback(callback &()){
log("Hello again from the worker thread\n");
callback~();
}~(endAppDelegate);
This code will print
Hello from the worker thread
Hello again from the worker thread
Shutdown from the main thread
Conclusion
Argentum really creates the multithreaded environment.
Runtime requires very little points of synchronization. Mutexes are locked only on thread creation/disposal, posting message and every 8000th operation on shared objects stored in fields.
Existing code can work on any thread without modifications.
Argentum threading model:
- isolates object mutations in threads in transactional manner
- eliminates data races
- makes it not necessary to use any synchronization objects like mutexes, atomics etc
- own-weak-frozen pointers keep working in multithreaded environment with the same semantics, at the full speed
- they help structure inter-thread communications and automate operations that otherwise require heavy-weight machinery (for example weak pointer to and object in a different thread, is a thread-safe COM-moniker or resource locator service, but it is represented just by a single machine-word pointer to the existing weak-block structure).
What's next with multithreading:
- a number of corner cases, optimizations and checks left not implemented yet
- cancellable-reportable task should be added to address tracking-cancelling scenarios
- thread-pool needs to be implemented.
- better syntax for delegate creation and posting.