Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to share local variable between threads (via a callback closure)?

I want to do something like the following - basically I am calling an async operation which will call a callback in another thread and I want to wait for it to complete "inline". My worry is that that changes variables shared across threads (bar & event) may not be synchronized due to being stored in registers for example. If they were member variables I could mark them volatile but volatile can’t be used on local variables created on the stack. I could use member variables but I think its cleaner not clutter my class by keeping it all local.

Bar bar = null;
ManualResetEvent event = new ManualResetEvent(false);

foo.AsyncOperation(new Action(()=>{    
    // This delegate will be called in another thread
    bar = ...
    event.Set();
}));

event.WaitOne(timeout);
// use bar
like image 300
Shane Avatar asked Sep 02 '11 11:09

Shane


People also ask

Can threads share local variables?

Tip: Unlike class and instance field variables, threads cannot share local variables and parameters. The reason: Local variables and parameters allocate on a thread's method-call stack. As a result, each thread receives its own copy of those variables.

Can global variables be shared between processes and threads?

Each process has its own global state. Can global variables be accessed from another thread? Yes, a thread who lives in the same process can access a global variable, but you must ensure the safety of any memory that is accessed by multiple threads.

How do you pass variables between threads?

We can share a local variable between threads using a queue. The queue must be shared and accessible to each thread and within the function where the local variable is defined and used. For example, we can first create a queue. Queue instance.


2 Answers

Yes, it will work correctly. Read here

http://www.albahari.com/threading/part4.aspx

The following implicitly generate full fences: Setting and waiting on a signaling construct

and in the signaling constructs the ManualResetEvent is included.

If you want to know what a full fence is, in the same page:

Full fences The simplest kind of memory barrier is a full memory barrier (full fence) which prevents any kind of instruction reordering or caching around that fence. Calling Thread.MemoryBarrier generates a full fence;

like image 77
xanatos Avatar answered Oct 05 '22 07:10

xanatos


I think your code will work - the closure will lift then into the heap even if they were just stack-variables (the ManualReseetEvent will surely not be).

But why don't you put everything after event.WaitOne() just inside the continuation (the block were event.Set is called)? I think this should be the prefered way to handle this kind of situation and you won't run into trouble this way (you don't need the Bar in the outer block at all, and you can still use the MRE to check).

I would consider turning this into Operations using Task objects - this would solve all of this in one go (for example return a Task from your AsyncOperation). You can then wait for the result of the Task and use the returned Bar ...

class Foo
{ 
 // ...
 private Task<Bar> AsyncOperation(Task<Bar> initializeBar)
 {
   return initializeBar
          .ContinueWith(
            bar => { 
                     /* do your work with bar and return some new or same bar */ 
                     return bar;
                   });
 }
}

and use it like this:

var task = foo.AsyncOperation(Taks.Factory.StartNew(() => { /* create and return a bar */ }));
var theBar = task.Result; // <- this will wait for the task to finish
// use your bar

PS: The closure will basically wrap them just into a class-object ;) PPS: it's hard for me to test this code without your AsyncOperation but it should work modulo syntax errors by wrong spelling/typing I made

like image 39
Random Dev Avatar answered Oct 05 '22 07:10

Random Dev