CoCoL is a fresh multi-programming approach, leveraging the C# await
keyword to produce sequential and easily understandable multithreading code. With a shared-nothing approach and explicit communication, programs written with CoCoL are automatically free from race conditions and other threading hazards.
If you are familiar with the Go Language, you can think of CoCoL as providing the Go programming model inside the CLR.
The NuGet package is the recommended way of installing CoCoL:
PM> Install-Package CoCoL
The most basic program with multithreading would be a producer/consumer setup, where one thread produces data, and another consumes it:
using System;
using System.Linq;
using System.Threading.Tasks;
using CoCoL;
class Example
{
static async Task Produce(IChannel<int> channel)
{
foreach (var i in Enumerable.Range(0, 5))
await channel.WriteAsync(i);
channel.Retire();
}
static async Task Consume(IChannel<int> channel)
{
try
{
while (true)
Console.WriteLine("Hello World: {0}", await channel.ReadAsync());
}
catch (RetiredException)
{
}
}
static void Main()
{
var channel = ChannelManager.CreateChannel<int>();
Task.WhenAll(
Produce(channel),
Consume(channel)
).Wait();
}
}
Output:
Hello World: 0
Hello World: 1
Hello World: 2
Hello World: 3
Hello World: 4
The producer writes a number of values into the channel, and then stops the channel. The consumer simply reads whatever is written into the channel.
Since the producer and consumer only shares the channel, and only communicate through the channel, no race conditions are possible.
A key feature of CoCoL is that it is capable of scaling to a very large number of channels and reader/writers. On a machine with 16GB of memory, it is possible to run more than 10 million reader/writer pairs. Using the above producer and consumer example, it is possible to attach multiple producers and consumers on the same channel, with no ill effects:
static void Main()
{
var channel = ChannelManager.CreateChannel<int>();
Task.WhenAll(
Enumerable.Range(0,4).Select(x => Produce(channel))
.Union(
Enumerable.Range(0,2).Select(x => Consume(channel))
)
).Wait();
}
In the above, there are four producers and two consumers sharing the same channel. The runtime adjusts how many threads it deems necessary for running the application.
If the producers each have their own channel, the consumer can choose to read from any of the channels:
static async Task Consume(IEnumerable<IChannel<int>> channels)
{
while(true)
Console.WriteLine("Hello World: {0}", await channels.ReadFromAnyAsync());
}
static void Main()
{
var channels = Enumerable.Range(0, 5).Select(x => {
var channel = ChannelManager.CreateChannel<int>();
Produce(channel);
return channel;
});
Consume(channels).Wait();
}
If existing code is used, it is possible to use blocking or probing calls as well:
static void Main()
{
var channel = ChannelManager.CreateChannel<int>();
var thread = new System.Threading.Thread(x => {
// Probing write
while(!channel.TryWrite(1))
{
// Do other stuff here
System.Thread.Thread.Sleep(1000);
}
});
thread.Run();
// Blocking read
channel.Read();
}
Look at the CommsTime, Sieve, and Mandelbrot examples.