Can a C# Thread Really Cache a Value and Ignore Changes to That Value on Other Threads

Can a C# thread really cache a value and ignore changes to that value on other threads?

The point is: it might work, but it isn't guaranteed to work by the spec. What people are usually after is code that works for the right reasons, rather than working by a fluke combination of the compiler, the runtime and the JIT, which might change between framework versions, the physical CPU, the platform, and things like x86 vs x64.

Understanding the memory model is a very very complex area, and I don't claim to be an expert; but people who are real experts in this area assure me that the behaviour you are seeing is not guaranteed.

You can post as many working examples as you like, but unfortunately that doesn't prove much other than "it usually works". It certainly doesn't prove that it is guaranteed to work. It would only take a single counter-example to disprove, but finding it is the problem...

No, I don't have one to hand.


Update with repeatable counter-example:

using System.Threading;
using System;
static class BackgroundTaskDemo
{
// make this volatile to fix it
private static bool stopping = false;

static void Main()
{
new Thread(DoWork).Start();
Thread.Sleep(5000);
stopping = true;


Console.WriteLine("Main exit");
Console.ReadLine();
}

static void DoWork()
{
int i = 0;
while (!stopping)
{
i++;
}

Console.WriteLine("DoWork exit " + i);
}
}

Output:

Main exit

but still running, at full CPU; note that stopping has been set to true by this point. The ReadLine is so that the process doesn't terminate. The optimization seems to be dependent on the size of the code inside the loop (hence i++). It only works in "release" mode obviously. Add volatile and it all works fine.

When can I guarantee value changed on one thread is visible to other threads?


Is it possible for a thread to change a cached value, which then never leaves its cache and so is never visible to other threads?

If we're talking literally about the hardware caches, then we need to talk about specific processor families. And if you're working (as seems likely) on x86 (and x64), you need to be aware that those processors actually have a far stronger memory model than is required for .NET. In x86 systems, the caches maintain coherency, and so no write can be ignored by other processors.

If we're talking about the optimization wherein a particular memory location has been read into a processor register and then a subsequent read from memory just reuses the register, then there isn't a similar analogue on the write side. You'll note that there's always at least one read from the actual memory location before we assume that nothing else is changing that memory location and so we can reuse the register.

On the write side, we've been told to push something to a particular memory location. We have to at least push to that location once, and it would likely be a deoptimization to always store the previously known value at that location (especially if our thread never reads from it) in a separate register just to be able to perform a comparison and elide the write operation.

Implementing a permanent thread that manages a resource and other threads that requests this thread to do some task and return a result

One way would be with a shared LimitedConcurrencyLevelTaskScheduler (see example). That could ensure only a single thread has exclusive access to the resource at any one time, while not needing the block a thread when the resource in not needed.

The typical way to write this functionality yourself would be with a blocking collection. I.e. the worker thread would run a foreach loop over the GetConsumingEnumerable and process each item. A very simplified example could look something like:

        private TResource resource;
private BlockingCollection<Action<TResource>> queue = new();
public void Start() => Task.Run(ThreadMain);
public Task<T> Enqueue<T>(Func<TResource, T> method)
{
var tcs = new TaskCompletionSource<T>();
queue.Add(r => tcs.SetResult(method(r)));
return tcs.Task;
}
private void ThreadMain()
{
foreach (var action in queue.GetConsumingEnumerable())
{
action(resource);
}
}

But you would need to add things like error handling, cancellation, stopping the thread etc.

Another alternative might be a semaphoreSlim with a limit of one, and use WaitAsync with async/await to ensure exclusive access. I don't think a semaphore would guarantee any particular ordering if that is important to you. You may also risk blocking the UI thread if the work takes time.

There are also frameworks like DataFlow that might help, but I'm not familiar enough with it to provide any recommendation.

For a introduction to task based programming, see Task-based asynchronous programming and Asynchronous programming with async and await.

Passing objects to different threads without using Queue

You don't explain what you want to do, so it's hard to give a specific answer. Running some heavy work in the background to avoid blocking the UI describes half of the concurrency problems. The other half has to do with avoiding long waits when a server takes too long to answer.

If you want to perform some long-running job in response to a user's action, eg a click, just use await and Task.Run :

public async void myButton_Click(object sender, EventArguments arg)
{
....
var result=await Task.Run(()=>calculate(someData);
toolStripLabel1.Text ="Step 1 complete";
var result2=await Task.Run(()=>someOtherCalculation(result));
txtBox1.Text=result2;
}

This will use a background thread from a threadpool to run the calculations and release the UI thread. When the calculation completes, execution will resume in the UI thread with the statement that comes after the await ... call.

async/await can be used to make asynchronous IO calls to databases, servers and files. In this case there is no background processing. Instead of blocking while waiting for the server or disk to respond, the UI thread is released and execution resumed when the asynchronous call completes :

public async void myButton_Click(object sender, EventArguments arg)
{
var client=new HttpClient();
....
var result=await client.GetStringAsync(someUrl);
txtBox1.Text=result;
}

In many cases, you want to queue some data for eventual processing, eg writing to a log file from multiple threads or tasks. Or, you may have a thread that produces data that another thread/task needs to consume without blocking the original thread.

There are numerous techniques and classes that can be used in this case.

Perhaps the most straigh-forward is to use the ActionBlock class from the TPL Dataflow library. It allows other threads to post messages to it asynchronously, it buffers the data and processes the messages using one or more tasks. By default, only one task is used :

var logBlock= new ActionBlock<string>(msg=>File.AppendLine("log.txt",msg));

logBlock.Post("Hello!");

An ActionBlock that uses eg 10 concurrent tasks can be used for example to send multiple HTTP requests at the same time

HttpClient client=new HttpClient();

var options=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism = 10};
var downloadBlock= new ActionBlock<Tuple<string,string>>(async msg=>{
var content=await client.GetStringAsync(msg.Item1);
await File.AppendText(msg.Item2,content));
}

downloadBlock.Post(Tuple.Create(someUrl,someFile));

One can specify more options, eg BoundedCapacity places an upper limit on the number of messages that can be queued, preventing overflow if the producer/poster is too fast.

ActionBlock is part of the TPL Dataflow library. Another interesting block is TransformBlock which returns a result in an output buffer. Multiple blocks can be linked so that each block executes a processing step in a different thread.

Another common scenario is Producer/Consumer. In this case, one thread produces data, places it "somewhere" and another thread consumes and processes that data.

That somewhere can be ConcurrentQueue< T>, one of the concurrent collections of .NET. These classes are thread-safe, which means that multiple threads can read and write to them concurrently without risking corruption.

var queue = new ConcurrentQueue<string>(10);

var producer = Task.Run(async ()=>{
for(i=0;i<100;i++)
{
queue.Enqueue($"Message {i}");
await Task.Delay(100);
}
});

var consumer = Task.Run(()=> {
while(true)
{
if (queue.TryDequeue(var out msg))
{
File.AppendLine("log.txt",msg);
}
}
});

You can avoid polling by using BlockingCollection whose Take method blocks if there are no messages. Internally, it uses a ConcurrentQueue, although that can be changed eg to a ConcurrentStack :

var queue = new BlockingCollection<string>(10);

var producer = Task.Run(async ()=>{
for(i=0;i<100;i++)
{
queue.Add($"Message {i}");
await Task.Delay(100);
}
queue.CompleteAdding();
});

var consumer = Task.Run(()=> {
while(!queue.IsCompleted)
{
var msg=queue.Take();
File.AppendLine("log.txt",msg);
}
});

Finally, the BufferBlock< T> class from TPL Dataflow provides ReceiveAsync so there's no need to block waiting for a message :

var queue = new BufferBlock<string>();

var producer = Task.Run(async ()=>{
for(i=0;i<100;i++)
{
queue.Post($"Message {i}");
await Task.Delay(100);
}
queue.Complete();
});

var consumer = Task.Run(async ()=> {
while(!queue.Completion.IsCompleted)
{
var msg=await queue.ReceiveAsync();
File.AppendLine("log.txt",msg);
}
});

C# and thread-safety of a bool

No, not all of them are thread safe.

Case one isn't actually completely thread safe, or better saying - it isn't thread safe at all. Even if operations with boolean are atomic, variable value can be stored in a cache, and so, as in multicore CPU each core has it's own cache, value can be potentially corrupted.

Going even further, compiler and CPU can perform some internal optimizations, including instruction reordering, which can harmfully affect your program's logic.

You can add the volatile keyword, to notify the compiler that this field is used in a multi-threaded context. It will fix problems with cache and instruction reordering, but doesn't give you truly "thread safe" code (as write operations still will be not synchronized). Also volatile cannot be applied to local variable.

So when dealing with multi-threading you always have to use some technique of thread synchronization on valuable resources.

For more information - read this answer, which has some deeper explanation of different techniques. (example there is about int, but is doesn't really matter, it describes general approach.)

Why does parallel this code work sometimes?

A thread is allowed by the spec to cache a value indefinitely.

see Can a C# thread really cache a value and ignore changes to that value on other threads? and also http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

If one thread writes to a location and another thread is reading, can the second thread see the new value then the old?

The answer seems to be, "this is exactly the job of the CPU cache coherency." x86 processors implement the MESI protocol, which guarantee that the second thread can't see the new value then the old.



Related Topics



Leave a reply



Submit