Instead of traditional locks and thread primitives to protect state. Why not let the objects themselves protect their own state with a sync primitive and simple single threaded scheduler all handled by the runtime?
Why wouldn't something like this work?
1) Have a new modifier for types. I call it "sync" here.
2) Allow write access to sync'd types only inside a sync(){} block - Like a critical section or lock, but different in operation.
The Sync on the type has a single worker thread and a ready queue (like a the monitor implements) and is a lock free queue. When you take a Sync(obj){} on a type, the runtime schedules your continuation on the ready queue for that type.
The sync worker thread then runs the continuations in the queue 1 by 1. That way, only 1 thread is ever
writing to state objects protected by sync(). The scheduling and queue logic are handled by the runtime.
From a language perspective, it has similar sugar to Async and the traditional lock. For readonly access, I am guessing you may be able to pull of a read only copy under the covers without a sync(), but maybe for symmetry you want a sync block as well. If you try to access a sync type outside of a sync(){} block, you get
a compile time error. Like Async, in that you can have continuations and code execute outside current thead and get a IAsyncResult back so that you don't have to block current thread. But the idea of having the actual object manage the access on a worker thread is new AFAICT.
Example:
// Type Sync'er
// sync modifier makes types so can only modify inside a sync block.
sync int num = 0;
for(int i=0; i<100; i++)
{
var result = sync(num) // Returns a copy of new state after writes in the block.
{
// Can only change state of a sync var inside a sync block.
num++;
}
Console.WriteLine(result.Value);
}
Properties of Sync:
1. Objects protected by sync modifier can not be changed or public properties changed unless inside a sync{} block. So object state is non-mutable outside of a sync block. The compiler and runtime both protect that rule.
2. Each object protected by sync has its' own ready queue managed by the runtime (like a monitor does).
3. A ready queue on an object has a single worker thread servicing the queue. This thread can be shared across multiple queues or other safe and fair threadpool design.
4. Reads and writes are fair as they are scheduled in FIFO order. This avoids the current issues with reader/writer locks and unfairness. Could also change scheduler algo as needed for app.
5. Exceptions in a sync block could optionally be fatal to any future queued up requests, or handled locally and remain silent to other tasks in the queue. This can be a good way to propagate errors across the system and shutdown other threads.
6. sync(){} block returns an ITask<T> which represents the scheduled task on the ready queue. So can read the readonly state of sync object <T> in the task result or cancel task or wait on task, etc.
7. Threads don't block waiting for sync results like a lock(). Threads schedule an async Task to run on the object's ready queue. Something like an Actor model, but in a natural and imperative way.In this way, any object is an "actor" and continuations are scheduled by the runtime for access to that actor.
8. Debugging is easier to reason about because of the single thread access to object state.
9. Child object of root object are automatically sync'd also and protected by root sync object. Because of this, dead locks and live locks are not a concern. Lock leveling concerns are avoided also.
10. Other sync'd objects can be associated with another sync object as the parent. In this way if objects are invariant to each other and must be treated as a "set" from a concurrency concern, they will use the same ready queue and thread.
11. The ready queue method is a single point for constraint rules and triggers. In this way, both pre and post conditions can be put on sync'd objects and rules enfored in a clear way. Notifications could be implemented as after update triggers on the sync object.
12. Remotable. Because if follows a request/reply async model, it can be remotable. For example, the Sync'd object could be a server object and the block of code is remoted to server and scheduled against the server's sync'd object. In this way, it can be a safe way to handle state on a server and keep a request/reply pattern. The sync'd object, for example, could be a URL to a shared sync object. The Task<T> based nature of sync allows async continuations as normal Async does today.
13. Transaction friendly. Because the state of the object can be captured by the runtime at any point, transactions could be integrated to allow rollbacks to some prior state. The queue allows ability to rewind and trace history of code blocks that effected object.
14. Coordination Primitives such as Choice can be setup as triggers on the Sync. This would allow continuations to run when some set of a rules are true. For example, schedule my sync block when both X and Y are true.
15. Sync can optimize reads. The root sync object is readonly by design. Can only write inside a sync block. Because of this, a single readonly instance can be kept handy internally. This instance can be handed out to readers by reference. After a write, a new readonly instance is the new reader reference in cache.
16. Sync leads code down the path to success and safe by construction.
17, A sync'd object could be constructed by a lazy function. As sync queue is single threaded, this also makes it easy for the runtime to guarantee safe and first use contruction. Such as:
sync MyObject myObj = { return new MyObject(...); } // lazy construction on first use.
18. Sync could allow explict termination of access to object either by exception or other such notication in the IAsyncResult.
So how is this different then just protecting an object with a ReaderWriter lock (RWL)?
1) ReaderWriter is a Volunteer or opt in approach. It is not safe by construction. Other theads using the lock must use in the correct way. Accessing the object outside of lock context is still possible.
2) RWL means blocking threads.
3) RWL is not fair. It either favors readers or writers.
4) RWL requires a memory barrier between threads as not single threaded. For writers, for example, this is a required big waste as only a single thread can be inside lock anyway, yet we pay the cost for multiple access with memory barriers and context switches.
5) RWL methods typically require other locks or waiting on events for higher order concurrency needs such as joining on all readers/writers.
So how is this different then Async?
1) Async provides no explicit concurency access to shared state. It just runs functions and continuations on a thread pool.
2) Async uses a threads, so back to using manual lock primitives inside task code.
So how is this different then a Monitor?
1) Monitor manages gate to object from multiple threads. Sync is by design single threaded and work is queued on that thread. Similar to UI message pump.
2) Monitor does not enforce correctness on the object uses. It is an opt in approach like RWL.
3) Monitors have no shared notion of passing exceptions across monitor users or explicit terminations.
So how is this different then the CCR?
1) The CCR excels at coordination. However it still leaves the task of locking on shared state to the user.
2) Both CCR and sync can use coordination primitives such as Choice.
What do you think?

Sync primitive by William Stacey is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.
Copyright 2012 William Stacey