Simple threading question, locking access to shared resource or entire function?
This is a paraphrasing of a question I had before. It's a simple threading question but I can't seem to understand it.
If i have shared code:
private static object objSync = new object();
private int a = 0;
public void function()
{
lock(objSync)
a += 2;
lock(objSync)
a += 3;
Console.WriteLine(a.ToString());
a=0;
}
I can't expect 'a' to equal 5 in the end because the first thread gains a lock sets 'a' to 2 and then then the next thread can get a lock set it to '4' before the first thread is able to add 3 and you'll end up with 7 in t开发者_开发技巧he end.
The solution as I understand it would be to put a lock around the entire thing and then you could always expect 5. Now my question is what if between the two locks there is a a million lines of code. I can't imagine putting a lock around a million lines of code. How would you ensure thread safety but not take the fail to performance by putting a lock around a million and two lines of code?
EDIT:
This is nonsense code that I wrote for the question. The actual application is a line monitoring system. There are two screens that show the current line, one for the clerks and one for the public. The screen for the clerk accepts 'clicks' through the serial port which progresses the line by one and then updates the public screen through a notify event (see observer design pattern). The problem is they aren't synchronized if you spam the clicker. I imagine what's happening is the first screen adds to the line number, displays it and then updates the public screen, but before the public screen has a chance to show the line number from the database, the clerk clicks again and the number goes out of synch. The 'a' above represents a value I retrieve from the db and not just a simple int. There are a few places that I change the value and I need all of it to happen atomically.
It all depends on what you define as "success". If it is a counter, that could still be the right result. If you only want to update it if it is what you thought it was, then take a snapshot inside the first lock, and likewise compare to the snapshot in the final lock. In that scenario you can replace much of the code with some Interlocked.CompareExchange. Likewise in the "counter" scenario, Interlocked.Increment is your friend.
If the code must match, then you have a few options:
- if the compare fails, run the entire million lines again; repeat until you win the race (by matching)
- if the compare fails, throw an exception
- block for the million lines; it might still be a fast million lines...
- think of some other resolution strategy that makes sense for your scenario
In the first two you should watch for polluting things by leaving any related data in an intermediate state.
And btw; the final assignment to 0 should probably also be part of the same locking strategy. Locks only work if all access respects them.
But only your scenario can tell us what the "right" behaviour in a conflict is.
It is a matter of design actually. If your method calculates different values for "a", depending on certain circumstances, but you do not want another thread changing "a"'s value while an operation is active, something like this is more appropriate:
private static object objSync = new object();
private int a = 0;
public void function()
{
int b;
lock(objSync)
b = a;
b += 2;
//million lines of code
b +=3;
lock(objSync)
{
a = b;
Console.WriteLine(a.ToString());
a=0;
}
}
精彩评论