问题
Assume we have a method like this
public static void method(string param)
{
** critical section **
// There are a lot of methods calls
// switch cases
// if conditions
// read and write in dictionary
// new class initiations
** critical section **
}
how we can make it thread safe while thousand of concurrent calls happen?
Could delegates help? I read here that
Modifying event is not thread-safe, but invoking a Delegate is thread-safe. Since a Delegate is immutable type so it is thread safe.
Does that mean that delegates make my code thread safe?
Here is a couple of concepts:
- in the above diagram, what's the correct use of
Locker
? Inside of method or outside of it? why? - Is
Lock
orMutex
faster? - When do
delegate
s come into play for thread safety ? - How is the shared resource part of the code determined?
Does Visual Studio have the ability to analyze where resources are shared and need to be made thread safe?
How to make acquired lock thread to release the lock after 2.5 sec and queue all other threads which need the lock?
回答1:
Read to end you will enjoy it.
Is Lock or Mutex faster?
using System;
using System.Diagnostics;
using System.Threading;
namespace LockingTest
{
class Program
{
public static object locker = new object();
public static Mutex mutex = new Mutex();
public static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
for (int i = 0; i < 10000000; i++)
{
mutex.WaitOne(); // we are testing mutex time overhead
mutex.ReleaseMutex();
}
sw.Stop();
Console.WriteLine("Mutex :" + " proccess time token " + sw.Elapsed.ToString() + " miliseconds");
Thread.Sleep(1000); // let os to be idle
sw.Restart();
for (int i = 0; i < 10000000; i++)
{
lock (locker) { } // we are testing lock time overhead
}
sw.Stop();
Console.WriteLine("Lock :" + " proccess time token " + sw.Elapsed.ToString() + " miliseconds");
Console.ReadLine();
}
}
}
if you copy and paste above code in visual stuido and run it you will see
as you can see lock
is 50x faster than mutex
How is the shared resource part of the code determined?
Does Visual Studio have the ability to analyze where resources are shared and need to be made thread safe?
of course YES, i have updated my visual studio 2010 to 2015,in visual studio 2015, when you look top of each method you will see references look below image. > Yes,if visual studio could to talk it would tell me "MAKE THIS METHOD SAFE".maybe in next updates of visual studio they do add this option. who knows?
When references to a method goes high the danger of memory corruption goes high and vise versa.
How to make acquired lock thread to release the lock after 2.5 sec and queue all other threads which need the lock?
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace LockReleaseTest
{
class Program
{
public static object locker = new object();
public static ManualResetEvent mre = new ManualResetEvent(false);
public static bool isWorkDone = false;
public class StateObject
{
public int ThreadNumber;
public string Criticla_Parameter;
public int ItTakes = 1000;
}
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
StateObject state = new StateObject();
state.ThreadNumber = i;
state.Criticla_Parameter = "critical " + i.ToString();
ThreadPool.QueueUserWorkItem(method, state);
}
Thread.Sleep(13000); // wait previous process to be done
Console.WriteLine("In order to test release lock after 2.5 sec press enter");
Console.ReadLine();
for (int i = 0; i < 5; i++)
{
StateObject state = new StateObject();
state.ThreadNumber = i;
state.ItTakes = (i + 1) * (1000);
state.Criticla_Parameter = "critical " + i.ToString();
ThreadPool.QueueUserWorkItem(method2, state);
}
Console.ReadLine();
}
public static void method(Object state)
{
lock (locker)
{
// critcal section
string result = ((StateObject)state).Criticla_Parameter;
int ThreadNumber = ((StateObject)state).ThreadNumber;
Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
// simultation of process
Thread.Sleep(2000);
Console.WriteLine("ThreadNumber is " + ThreadNumber + " Result of proccess : " + result);
// critcal section
}
}
public static void method2(Object state)
{
if (Monitor.TryEnter(locker, -1))
{
mre.Reset();
ThreadPool.QueueUserWorkItem(criticalWork, state);
Thread.Sleep(200);
ThreadPool.QueueUserWorkItem(LockReleaser, ((StateObject)state).ThreadNumber);
mre.WaitOne();
Monitor.Exit(locker);
}
}
public static void criticalWork(Object state)
{
isWorkDone = false;
string result = ((StateObject)state).Criticla_Parameter;
int ThreadNumber = ((StateObject)state).ThreadNumber;
int HowMuchItTake = ((StateObject)state).ItTakes;
// critcal section
Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
// simultation of process
Thread.Sleep(HowMuchItTake);
Console.WriteLine("ThreadNumber " + ThreadNumber + " work done. critical parameter is : " + result);
isWorkDone = true;
mre.Set();
// critcal section
}
public static void LockReleaser(Object ThreadNumber)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
do
{
if (isWorkDone) return; // when work is done don't release lock // continue normal
} while (sw.Elapsed.Seconds <= 2.5); // timer in order to release lock
if (!isWorkDone) // more than 2.5 sec time took but work was not done
{
Console.WriteLine("ThreadNumber " + ThreadNumber + " work NOT done. Lock must be released ");
mre.Set();
}
}
}
}
If you copy and paste the above code in visual studio and run it you will get a result looks like this
As you can see ,in the first processes we do not release lock and all the threads enters sequentially to critical section but in the second process we do release the lock when process goes long time and when the lock is released next thread(Thread 2) enters and acquires the lock.
Because, the lock must be released in parent thread then we use ManualEventRest
to signal the parent to release the lock. i tried other approaches but they didn't work and exception of SynchronizationLockException
happens .This is the best approach that i found without throwing exception.
If this post is useful don't forget to vote up.sincerely yours
回答2:
I am taking the liberty of adding a second answer as it now appears that a key part of the question was how to cancel a lock (i.e. release it after a few seconds).
However, it doesn't make sense to cancel a lock (from "outside" of the lock) without cancelling the work that is being done inside the lock. If you do not cancel the work that is being done inside the lock then it may attempt continued access to the critical resource, resulting in two threads using the resource at the same time. What one should do, instead of breaking the lock from outside, one should cancel the work being done, which then will result in the lock being exited by that worker.
A comment on threading and cancellation. One should not abort threads, because in general it leaves the program (e.g. the resources held by that thread) in an undefined state. It has been a number of years since tasks and task cancellation have been introduced. A Task is essentially an operation or method that is queued to be executed, along with other Tasks, on threads obtained from, for example, the thread pool. These days pretty much all recent code should be based on tasks and follow the cooperative task cancellation approach. The following code shows how to do this, including starting the task on the thread pool.
Note I am using the MethodLock class introduced in my earlier answer; this is just a wrapper for SemaphoreSlim.
Here is a Worker class that does some work with a critical resource (and some without the resource). It cooperates in cancellation by testing the CancellationToken every once in a while. If cancellation was requested then the worker cancels itself by throwing a special exception.
public class Worker
{
public Worker(int workerId, CancellationToken ct, int howMuchWorkToDo)
{
this.WorkerId = workerId;
this.CancellationToken = ct;
this.ToDo = howMuchWorkToDo;
this.Done = 0;
}
public int WorkerId { get; }
public CancellationToken CancellationToken { get; }
public int ToDo { get; }
public int Done { get; set; }
static MethodLock MethodLock { get; } = new MethodLock();
public async Task DoWorkAwareAsync()
{
this.CancellationToken.ThrowIfCancellationRequested();
this.Done = 0;
while (this.Done < this.ToDo) {
await this.UseCriticalResourceAsync();
await this.OtherWorkAsync();
this.CancellationToken.ThrowIfCancellationRequested();
this.Done += 1;
}
Console.WriteLine($"Worker {this.WorkerId} completed {this.Done} out of {this.ToDo}");
}
private async Task UseCriticalResourceAsync()
{
using (await MethodLock.LockAsync()) {
//Console.WriteLine($"Worker {this.WorkerId} acquired lock on critical resource.");
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
}
private async Task OtherWorkAsync()
{
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
}
Now let's look at how to start a number of background workers and keep them from running too long, i.e. cancelling them after a few seconds. Note this is set up as a console application.
The tasks are put onto the thread pool, which means that the system will allocate the tasks among available threads. The system can also dynamically reallocate the tasks to threads if needed, for example if a task is queued to a thread that is busy while another thread becomes free.
static void Main(string[] args)
{
Random rand = new Random( DateTime.Now.Millisecond);
Console.WriteLine("---- Cancellation-aware work");
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++) {
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(2000));
int howMuchWork = (rand.Next() % 10) + 1;
Worker w = new Worker(i, cts.Token, howMuchWork);
tasks[i] = Task.Run(
async () => {
try {
await w.DoWorkAwareAsync();
} catch (OperationCanceledException) {
Console.WriteLine($"Canceled worker {w.WorkerId}, work done was {w.Done} out of {w.ToDo}");
}
},
cts.Token
);
}
try {
Task.WaitAll(tasks);
} catch (AggregateException ae) {
foreach (Exception e in ae.InnerExceptions) {
Console.WriteLine($"Exception occurred during work: {e.Message}");
}
}
Console.ReadKey();
}
I would comment that the presence of "cts.Token" as the second argument to Task.Run does NOT relate to a forced / hard cancellation of the task created by the Task.Run method. All that Task.Run does with this second argument is compare it to the cancellation token inside the cancellation exception, and if it is the same then Task.Run transitions the task to the Canceled state.
When you run this you'll see something like the following:
---- Cancellation-aware work
Worker 5 completed 1 out of 1
Worker 2 completed 1 out of 1
Worker 8 completed 1 out of 1
Worker 6 completed 3 out of 3
Worker 7 completed 3 out of 3
Canceled worker 3, work done was 4 out of 5
Canceled worker 4, work done was 4 out of 10
Canceled worker 1, work done was 4 out of 8
Canceled worker 9, work done was 4 out of 7
Canceled worker 0, work done was 5 out of 9
Again, this design assumes that the worker methods cooperate with the cancellation. If you are working with legacy code where the worker operation does not cooperate in listening for cancellation requests then it may be necessary to create a thread for that worker operation. This requires proper cleanup and in addition it can create performance problems because it uses up threads, which are a limited resource. The response by Simon Mourier in the middle of this linked discussion shows how to do it: Is it possible to abort a Task like aborting a Thread (Thread.Abort method)?
回答3:
The response by @romen is useful in discussing the overall ideas. In terms of concretely doing the locking, let's look at a few different situations and solutions. I'm assuming that we are using C# here. In addition I will generally take the perspective of writing a class that needs to use locking within itself to make sure that consistency is preserved.
Thread locking only. In this scenario, you have multiple threads and only want to prevent two different threads from altering the same part of memory (say a double) at the same time, which would result in corrupted memory. You can just use the "lock" statement in C#. HOWEVER, in contemporary programming environments this is not as useful as you might think. The reason is that within a "lock" statement there are a variety of ways of calling back into external code (i.e. code that is outside the class) and this external code may then call back into the lock (possibly asynchronously). In that situation the second time the "lock" statement is encountered, the flow may well pass right into the lock, regardless of the fact that the lock was already obtained. This is often not at all what you want. It will happen whenever the second call on the lock happens to occur on the same thread as the first call. And that can happen quite easily because C# is full of Tasks, which are basically units of work that can execute, block for other Tasks, etc all on a single thread.
Task locking for the purpose of preserving the state consistency of the object. In this scenario, there are a set of private fields within the class that must have a certain invariant relationship to each other both before and after each class method is called. The changes to these variables is done through straight-line code, specifically with no callbacks to code outside of the class and no asynchronous operations. An example would be a concurrent linked list, say, where there is a _count field and also _head and _tail pointers that need to be consistent with the count. In this situation a good approach is to use SemaphoreSlim in a synchronous manner. We can wrap it in a few handy classes like this --
public struct ActionOnDispose : IDisposable
{
public ActionOnDispose(Action action) => this.Action = action;
private Action Action { get; }
public void Dispose() => this.Action?.Invoke();
}
public class StateLock
{
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public bool IsLocked => this.Semaphore.CurrentCount == 0;
public ActionOnDispose Lock()
{
this.Semaphore.Wait();
return new ActionOnDispose(() => this.Semaphore.Release());
}
}
The point of the StateLock class is that the only way to use the semaphore is by Wait, and not by WaitAsync. More on this later. Comment: the purpose of ActionOnDispose is to enable statements such as "using (stateLock.Lock()) { … }".
- Task-level locking for the purpose of preventing re-entry into methods, where within a lock the methods may invoke user callbacks or other code that is outside the class. This would include all cases where there are asynchronous operations within the lock, such as "await" -- when you await, any other Task may run and call back into your method. In this situation a good approach is to use SemaphoreSlim again but with an asynchronous signature. The following class provides some bells and whistles on what is essentially a call to SemaphoreSlim(1,1).WaitAsync(). You use it in a code construct like "using (await methodLock.LockAsync()) { … }". Comment: the purpose of the helper struct is to prevent accidentally omitting the "await" in the using statement.
public class MethodLock
{
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public bool IsLocked => this.CurrentCount == 0;
private async Task<ActionOnDispose> RequestLockAsync()
{
await this.Semaphore.WaitAsync().ConfigureAwait(false);
return new ActionOnDispose( () => this.Semaphore.Release());
}
public TaskReturningActionOnDispose LockAsync()
{
return new TaskReturningActionOnDispose(this.RequestLockAsync());
}
}
public struct TaskReturningActionOnDispose
{
private Task<ActionOnDispose> TaskResultingInActionOnDispose { get; }
public TaskReturningActionOnDispose(Task<ActionOnDispose> task)
{
if (task == null) { throw new ArgumentNullException("task"); }
this.TaskResultingInActionOnDispose = task;
}
// Here is the key method, that makes it awaitable.
public TaskAwaiter<ActionOnDispose> GetAwaiter()
{
return this.TaskResultingInActionOnDispose.GetAwaiter();
}
}
What you don't want to do is freely mix together both LockAsync() and Lock() on the same SemaphoreSlim. Experience shows that this leads very quickly to a lot of difficult-to-identify deadlocks. On the other hand if you stick to the two classes above you will not have these problems. It is still possible to have deadlocks, for example if within a Lock() you call another class method that also does a Lock(), or if you do a LockAsync() in a method and then the called-back user code tries to re-enter the same method. But preventing those reentry situations is exactly the point of the locks -- deadlocks in these cases are "normal" bugs that represent a logic error in your design and are fairly straightforward to deal with. One tip for this, if you want to easily detect such deadlocks, what you can do is before actually doing the Wait() or WaitAsync() you can first do a preliminary Wait/WaitAsync with a timeout, and if the timeout occurs print a message saying there is likely a deadlock. Obviously you would do this within #if DEBUG / #endif.
Another typical locking situation is when you want some of your Task(s) to wait until a condition is set to true by another Task. For example, you may want to wait until the application is initialized. To accomplish this, use a TaskCompletionSource to create a wait flag as shown in the following class. You could also use ManualResetEventSlim but if you do that then it requires disposal, which is not convenient at all.
public class Null { private Null() {} } // a reference type whose only possible value is null.
public class WaitFlag
{
public WaitFlag()
{
this._taskCompletionSource = new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public WaitFlag( bool value): this()
{
this.Value = value;
}
private volatile TaskCompletionSource<Null> _taskCompletionSource;
public static implicit operator bool(WaitFlag waitFlag) => waitFlag.Value;
public override string ToString() => ((bool)this).ToString();
public async Task WaitAsync()
{
Task waitTask = this._taskCompletionSource.Task;
await waitTask;
}
public void Set() => this.Value = true;
public void Reset() => this.Value = false;
public bool Value {
get {
return this._taskCompletionSource.Task.IsCompleted;
}
set {
if (value) { // set
this._taskCompletionSource.TrySetResult(null);
} else { // reset
var tcs = this._taskCompletionSource;
if (tcs.Task.IsCompleted) {
bool didReset = (tcs == Interlocked.CompareExchange(ref this._taskCompletionSource, new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously), tcs));
Debug.Assert(didReset);
}
}
}
}
}
- Lastly, when you want to atomically test and set a flag. This is useful when you want to execute an operation only if it is not already being executed. You could use the StateLock to do this. However a lighter-weight solution for this specific situation is to use the Interlocked class. Personally I find Interlocked code is confusing to read because I can never remember which parameter is being tested and which is being set. To make it into a TestAndSet operation:
public class InterlockedBoolean
{
private int _flag; // 0 means false, 1 means true
// Sets the flag if it was not already set, and returns the value that the flag had before the operation.
public bool TestAndSet()
{
int ifEqualTo = 0;
int thenAssignValue = 1;
int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
return oldValue == 1;
}
public void Unset()
{
int ifEqualTo = 1;
int thenAssignValue = 0;
int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
if (oldValue != 1) { throw new InvalidOperationException("Flag was already unset."); }
}
}
I would like to say that none of the code above is brilliantly original. There are many antecedents to all of them which you can find by searching enough on the internet. Notable authors on this include Toub, Hanselman, Cleary, and others. The "interlocked" part in WaitFlag is based on a post by Toub, I find it a bit mind-bending myself.
Edit: One thing I didn't show above is what to do when, for example, you absolutely have to lock synchronously but the class design requires MethodLock instead of StateLock. What you can do in this case is add a method LockOrThrow to MethodLock which will test the lock and throw an exception if it cannot be obtained after a (very) short timeout. This allows you to lock synchronously while preventing the kinds of problems that would occur if you were mixing Lock and LockAsync freely. Of course it is up to you to make sure that the throw doesn't happen.
Edit: This is to address the specific concepts and questions in the original posting.
(a) How to protect the critical section in the method. Putting the locks in a "using" statement as shown below, you can have multiple tasks calling into the method (or several methods in a class) without any two critical sections executing at the same time.
public class ThreadSafeClass {
private StateLock StateLock { get; } = new StateLock();
public void FirstMethod(string param)
{
using (this.StateLock.Lock()) {
** critical section **
// There are a lot of methods calls but not to other locked methods
// Switch cases, if conditions, dictionary use, etc -- no problem
// But NOT: await SomethingAsync();
// and NOT: callbackIntoUserCode();
** critical section **
}
}
public void SecondMethod()
{
using (this.StateLock.Lock()) {
** another, possibly different, critical section **
}
}
}
public class ThreadSafeAsyncClass {
private MethodLock MethodLock { get; } = new MethodLock();
public async Task FirstMethodAsync(string param)
{
using (await this.MethodLock.LockAsync()) {
** critical section **
await SomethingAsync(); // OK
callbackIntoUserCode(); // OK
}
}
public async Task SecondMethodAsync()
{
using (await this.MethodLock.LockAsync()) {
** another, possibly different, critical section using async **
}
}
}
(b) Could delegates help, given that Delegate is a thread-safe class? Nope. When we say that a class is thread-safe, what it means is that it will successfully execute multiple calls from multiple threads (usually they actually mean Tasks). That's true for Delegate; since none of the data in the Delegate is changeable, it is impossible for that data to become corrupted. What the delegate does is call a method (or block of code) that you have specified. If the delegate is in the process of calling your method, and while it is doing that another thread uses the same delegate to also call your method, then the delegate will successfully call your method for both threads. However, the delegate doesn't do anything to make sure that your method is thread-safe. When the two method calls execute, they could interfere with each other. So even though Delegate is a thread-safe way of calling your method, it does not protect the method. In summary, delegates pretty much never have an effect on thread safety.
(c) The diagram and correct use of the lock. In the diagram, the label for "thread safe section" is not correct. The thread safe section is the section within the lock (within the "using" block in the example above) which in the picture says "Call Method". Another problem with the diagram is it seems to show the same lock being used both around the Call Method on the left, and then also within the Method on the right. The problem with this is that if you lock before you call the method, then when you get into the method and try to lock again, you will not be able to obtain the lock the second time. (Here I am referring to task locks like StateLock and MethodLock; if you were using just the C# "lock" keyword then the second lock would do nothing because you would be invoking it on the same thread as the first lock. But from a design point of view you wouldn't want to do this. In most cases you should lock within the method that contains the critical code, and should not lock outside before calling the method.
(d) Is Lock or Mutex faster. Generally the speed question is hard because it depends on so many factors. But broadly speaking, locks that are effective within a single process, such as SemaphoreSlim, Interlocked, and the "lock" keyword, will have much faster performance than locks that are effective across processes, like Semaphore and Mutex. The Interlocked methods will likely be the fastest.
(e) Identifying shared resources and whether visual studio can identify them automatically. This is pretty much intrinsic to the challenge of designing good software. However if you take the approach of wrapping your resources in thread-safe classes then there will be no risk of any code accessing those resources other than through the class. That way, you do not have to search all over your code base to see where the resource is accessed and protect those accesses with locks.
(f) How to release a lock after 2.5 seconds and queue other requests to access the lock. I can think of a couple of ways to interpret this question. If all you want to do is make other requests wait until the lock is released, and in the lock you want to do something that takes 2.5 seconds, then you don't have to do anything special. For example in the ThreadSafeAsyncClass above, you could simply put "await Task.Delay( Timespan.FromSeconds(2.5))" inside the "using" block in FirstMethodAsync. When one task is executing "await FirstMethodAsync("")" then other tasks will wait for the completion of the first task, which will take about 2.5 seconds. On the other hand if what you want to do is have a producer-consumer queue then what you should do is use the approach described in StateLock; the producer should obtain the lock just briefly while it is putting something into the queue, and the consumer should also obtain the lock briefly while it is taking something off the other end of the queue.
回答4:
A lot of questions have been asked, but I will try to address all of them.
How we can make it thread safe while thousand of concurrent calls happen?
To make a method entirely thread safe, you can write it so that it has no side-effects. A method with no side-effects would not access any shared resources.
Could delegates help? Does that mean that delegates make my code thread safe? When do delegates come into play for thread safety ?
Delegates in C# are similar to function pointers in c++. They allow you to assign a method to a variable, and then call that method by invoking it through that variable. The only "thread safe" guarantee you get from using delegates is that the moment the delegate is invoked it will successfully call the function assigned to it. The invoked function executes exactly as it would if you had hard-coded a call to it instead in the same place.
in the above diagram, what's the correct use of Locker? Inside of method or outside of it? why?
I would argue that both options are less than ideal for placing a lock. The purpose of a synchronization object is to prevent simultaneous access to a resource. Every shared resource should have its own lock, and the best place to use those locks is around the few critical lines where its associated resource is actually used. If you always put the lock around the entire function body then you are likely blocking other threads for longer than necessary, which brings down the overall performance.
Is Lock or Mutex faster?
They serve different purposes.
The lock
statement is part of the C# language. Using this keyword cleans up your code and clearly outlines the critical section. According to this answer a lock
statement costs at least ~50ns so it's not much to worry about anyways.
On the other hand, a Mutex is an object that can be shared between processes so it is intended to be used for IPC. I don't see any reason to give up the lock
syntax in favour of Mutex
if you are not using it for IPC.
How is the shared resource part of the code determined?
I'll give an analogy to help you identify shared resources.
Imagine that your threads are workers on a construction site. The site has a portable toilet, and some power tools. Each worker has a different job to do so they grab their respective tools (not shared) and go to work. At some point each of these workers will have to use the toilet. The toilet has a lock to guarantee that only one worker uses it at a time. If the toilet is locked when another worker needs it, then they line up and wait for it to be unlocked.
In this analogy the power tools may be private class variables or objects that only one thread needs to access. While the toilet is an object that more than one thread will have to access at some point. That makes it a shared resource.
Does Visual Studio have the ability to analyze where resources are shared and need to be made thread safe?
Run the code in a debugger and see what breaks! The debugger will help you identify threading problems like deadlocks, and while paused you can see what method each thread is currently executing. If you see two threads working with the same variable then it's a shared resource.
How to make acquired lock thread to release the lock after 2.5 sec and queue all other threads which need the lock?
This question should really be its own post.
If a thread locks something, it is responsible for unlocking it. If the locked section is taking too long then there may just be a problem with your design. Implementing a timer to "cut off" a thread that has a lock is a dangerous design. Instead, you could place "checkpoints" in your thread method that check if it has executed for too long, using a timer started at the beginning of the method. If it needs to exit, it should release the lock and exit the method early so that it no longer accesses shared resources.
Using the lock
syntax automatically causes other threads to wait for the lock to be free. If multiple threads need the same lock then the order that they receive the lock is not guaranteed.
回答5:
Here is an example. The _sharedString
may be accessed by two functions, MethodAdd
and MethodDelete
which may be called from different threads. To ensure that access to the _sharedString
is serialized, that is, one thread at a time, we typically create a lock object, and then use the C# lock
keyword to get exclusive access to the shared resource, in this case _sharedString
.
private static object _lock = new object();
private string _sharedString = "";
public void MethodAdd(string s)
{
lock(_lock)
{
// Exclusive access to _sharedString.
_sharedString = _sharedString + s;
}
}
public void MethodDelete(int n)
{
lock (_lock)
{
// Exclusive access to _sharedString.
_sharedString = _sharedString.Substring(0, n);
}
}
You mention in your question By thread safe I mean that I want multiple concurrent operations - whereby none of them will be blocking each other, but this is impossible to achieve. There will always be a certain amount of blocking in order to achieve thread-safety. If your server is becoming too slow because of lock
(which you did not mention in your question, but in comments only), then you should revise your design; your shared resource is the bottleneck.
来源:https://stackoverflow.com/questions/57144771/how-to-synchronize-method-accessthread-safe-by-using-lock-and-delegates