1.2 Thread Foundation (II)

Keywords: Programming

1.2.1 lock keyword

Use the lock keyword to ensure that when a thread uses certain resources, other threads cannot use them at the same time.

class Counter
{
    private readonly object _syncRoot = new object();
    public int Count { get; private set; }

    public void Add()
    {
        lock (_syncRoot)
        {
            Count++;
        }
    }

    public void Sub()
    {
        lock (_syncRoot)
        {
            Count--;
        }
    }
}


var c = new Counter();
//Create multiple threads to access c
Thread t1 = new Thread(()=> TestCounter(c));
Thread t2 = new Thread(() => TestCounter(c));
Thread t3 = new Thread(() => TestCounter(c));
Thread t4 = new Thread(() => TestCounter(c));
Thread t5 = new Thread(() => TestCounter(c));
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t5.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
t5.Join();
Console.WriteLine("count:"+c.Count);

Finally output count:0, some people may say that this is not normal, then please do your own work to remove the Counter class lock part and try again, the final output will be different!~
It turns out that:

public void Add()
{
    Count++;
}

public void Sub()
{
    Count--;
}

This is because if the lock part is removed, the Counter class is not. Thread Safety .
When multiple threads access counter objects at the same time,

The counter value of the first thread is 10 and increases to 11.
The second thread gets 11 and increases to 12.
The first thread gets a counter value of 12, but before the descending operation occurs,
The second thread gets a counter value of 12,
The first thread decreases 12 to 11 and stores them back in counter.
At the same time, the second thread performs the same operation.

As a result, we did two incremental operations, but only the first descending operation.
This is clearly wrong.
This situation is called Race condition Competition conditions are a common cause of errors in multithreaded environments.

To ensure that this does not happen, all other threads must wait until the current thread completes the operation when a thread operates on the counter object. We can use the lock keyword to achieve this behavior. If an object is locked, all other threads that need to access the object will be blocked and wait to know that the object is unlocked, but this may lead to Causing serious performance problems.

1.2.2 Lock Resources Using Monitor Class

Deadlock It is also a common mistake in multithreaded programming. Since deadlock will cause program to stop working, Monitor class can be used to avoid deadlock.

class Program
{
    static void Main(string[] args)
    {
        object l1 = new object();
        object l2 = new object();

        new Thread(()=> LockObject(l1,l2)).Start();
        lock (l2)
        {
            Console.WriteLine("Monitor.TryEnter Deadlock avoidance,Return after timeout false");
            Thread.Sleep(1000);
            if (Monitor.TryEnter(l1,TimeSpan.FromSeconds(5)))
            {
                Console.WriteLine("Successful access to a protected resource!");
            }
            else
            {
                Console.WriteLine("Access resource timeout!");
            }
        }

        Console.WriteLine("------------------------------------");

        new Thread(() => LockObject(l1, l2)).Start();
        lock (l2)
        {
            Console.WriteLine("This will be a deadlock!");
            Thread.Sleep(1000);
            lock (l1)
            {
                Console.WriteLine("Successful access to a protected resource!");
            }
        }

        Console.WriteLine("End.");
        Console.ReadKey();
    }

    static void LockObject(object l1,object l2)
    {
        lock (l1)
        {
            Thread.Sleep(1000);
            lock (l2);
        }
    }
}

1.2.3 Handling Exceptions

It is important to always use try/catch blocks in a thread because it is impossible to catch exceptions outside the thread code.

class Program
{
    static void Main(string[] args)
    {
        var t = new Thread(ThreadOne);
        t.Start();
        t.Join();

        try
        {
            t = new Thread(ThreadTwo);
            t.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Main:{ex.Message}");
        }

    }

    static void ThreadOne()
    {
        try
        {
            Console.WriteLine("Starting a faulty thread...");
            Thread.Sleep(1000);
            throw new Exception("Boom!!!");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ThreadOne:{ex.Message}");
        }
    }

    static void ThreadTwo()
    {
        Console.WriteLine("Starting a faulty thread...");
        Thread.Sleep(1000);
        throw new Exception("Boom!!!");
    }

}

As can be seen from the above figure, ThreadOne's exception is caught, but ThreadTwo's exception is not caught.

Posted by celsoendo on Sun, 16 Jun 2019 15:40:32 -0700