Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shared Resource Lock

I'm using C# on .NET 4.5 - Web API Self Host. Server-side I've a process that is not thread safe: it can serve one request at a time. How can I lock this resource (process) in the controller code, so that clients are served sequentially, waiting for the resource to be freed before using it? Something like:

while(true){
    if(!process.isLocked)
        break;
}

lock(process)
do(work)
unlock(process)
warn(others)

Any code snippet or suggestion are appreciated. Thanks in advance.

like image 229
user1824269 Avatar asked Feb 19 '23 10:02

user1824269


2 Answers

If you're looking for each thread to execute, but only one at a time, then you can use the lock statement with a static object:

private static object lockobj = new object();

public void DoWorkWhenNotBusy()
{
    lock (lockobj)
    {
        // do work
        Console.WriteLine("Working #1 (should always complete)...");
        Thread.Sleep(500);
    }
}

If you want the thread to return immediately if the object is locked, then you can write it like this (double-checked locking):

private static object lockobj2 = new object();
private volatile bool locked = false;

public void DoWorkIfNotBusy()
{
    if (!locked)
    {
        lock (lockobj2)
        {
            if (!locked)
            {
                locked = true;
                // do work
                Thread.Sleep(500);
                Console.WriteLine("Working #2 (only if not busy)...");
            }
            locked = false;
        }
    }
}

Test example:

for (int i = 0; i < 10; i++)
{
    var ts = new ThreadStart(DoWorkWhenNotBusy);
    Thread t = new Thread(ts);
    t.Start();

    var ts2 = new ThreadStart(DoWorkIfNotBusy);
    Thread t2 = new Thread(ts2);
    t2.Start();

    Console.WriteLine("{0} started", i);
}
like image 136
McGarnagle Avatar answered Feb 27 '23 23:02

McGarnagle


I would like to expand on dbaseman's answer here. Specifically, his DoWorkIfNotBusy method.

I think that is a bad example (due to possible race conditions), and would like to show the way I think the correct way would be:

private static readonly object lockobj = new object();

public bool DoWorkIfNotBusy()
{
    bool lockWasTaken = false;
    var temp = lockobj;
    try
    {
        Monitor.TryEnter(temp, ref lockWasTaken);

        if (lockWasTaken) // This crucial test was missing! (Added 2021-04-08)
        {
            //Do work here.. 
        }
    }
    finally
    {
        if (lockWasTaken) Monitor.Exit(temp);
    }
    return lockWasTaken;
}

Updated 2021-04-08: Added the test to see if the current thread succeeded in acquiring the lock. If not, the code in the critical section should not be executed.


Updated 2021-04-09: Below code that shows the need for checking lockWasTaken, as a unit test.

[TestClass]
public class DummyTests
{
    private static readonly object LockObj = new object();

    [TestMethod]
    public void TestMonitor()
    {
        Thread trd1 = StartThread();
        Thread.Sleep(100);
        Thread trd2 = StartThread();
        Thread.Sleep(1000);
        Thread trd3 = StartThread();

        while (trd1.IsAlive || trd2.IsAlive || trd3.IsAlive)
        {
            Thread.Sleep(100);
        }
    }

    private Thread StartThread()
    {
        var thread = new Thread(parameter => this.ThreadTask());
        thread.Start(nameof(thread));
        Trace.WriteLine($"Started thread {thread.ManagedThreadId}.");
        return thread;
    }

    private void ThreadTask()
    {
        bool lockAcquired = false;
        try
        {
            Monitor.TryEnter(LockObj, ref lockAcquired);
            if (lockAcquired)
            {
                Trace.WriteLine($"Lock acquired by thread {Thread.CurrentThread.ManagedThreadId}.");
                Thread.Sleep(1000);
            }
            else
            {
                Trace.WriteLine($"Lock denied to thread {Thread.CurrentThread.ManagedThreadId}.");
            }
        }
        finally
        {
            if (lockAcquired)
            {
                Monitor.Exit(LockObj);
                Trace.WriteLine($"Lock released by thread {Thread.CurrentThread.ManagedThreadId}.");
            }
        }
    }
}

The output will be like this:

Started thread 12.
Lock acquired by thread 12.
Started thread 13.
Lock denied to thread 13.
Lock released by thread 12.
Started thread 14.
Lock acquired by thread 14.
Lock released by thread 14.
like image 42
caesay Avatar answered Feb 27 '23 23:02

caesay