开发者

How to properly lock an object

I am just reading a great tutorial about threads and have a problem with locks. I need some tip/advice that will point me in right direction. I'd like to understand why the output isn't ordered as i expect. The code shows my simple example.

  class Program {
    class A {
      public object obj = new object();
      public int i;
    }
    class B {
      public object obj = new object();
      public int j;
    }
    static void Main() {
      Console.Write("Thread1: ");
      A a = new A();
      for (a.i = 0; a.i < 9; a.i++) {
        lock (a) {
          new Thread(() => { Console.Write(a.i); }).Start();
        }
      开发者_运维知识库}
      Thread.Sleep(500);
      Console.Write("\nThread2: ");
      B b = new B();
      for (b.j = 0; b.j < 9; b.j++) {
        new Thread(() => { lock (b) { Console.Write(b.j); } }).Start();
      }
      Console.ReadLine();
    }
  }

Example output:
Thread1: 222456799
Thread2: 233357889

Link to the tutorial:
http://www.albahari.com/threading/


You are only locking while you create the thread, or (in the second case), access the value. Locks must be used by all threads, otherwise they do nothing. It is the act of trying to acquire the lock that blocks. Even if you did lock in both threads, that wouldn't help you marry each thread to the value of a.i (etc) at a particular point in time (that no longer exists).

Equally, threads work at their own pace; you cannot guarantee order unless you have a single worker and queue; or you implement your own re-ordering.

it will run at its own pace, and since you are capturing the variable a, it is entirely likely that the field a.i has changed by the time the thread gets as far as Console.Write. Instead, you should capture the value, by making a copy:

  A a = new A();
  for (a.i = 0; a.i < 9; a.i++) {
    var tmp = a.i;
    new Thread(() => { Console.Write(tmp); }).Start();
  }

(or probably remove a completely)

  for (int i = 0; i < 9; i++) {
    var tmp = i;
    new Thread(() => { Console.Write(tmp); }).Start();
  }


there are several issues here:

First, you are locking on a when you create a thread, so the thread is created, but your original main thread then releases the lock and keeps on trucking in the loop, while the created threads run concurrently.

You want to move the first lock into the thread that uses A to the Thread delegate like this:

for(a.i=0;a.i<9;a.i++)
{
  int id=a.i;
  new Thread(()=>{ lock(a){Console.Out.WriteLine("Thread{0} sees{1}",id,a.i)};}).Start(); // lots of smileys here :)
}

If you look closely, you will notice that the threads are not locked the same way for A and B, which tells you that threads live their own lives and Thread creation != Thread life.

Even with locking your thread runners, you can and will end-up in situations where thread 1 runs AFTER thread 2... but they will never run at the same time thanks to your lock.

You also reference a shared member in all your threads: a.i. This member is initialized in the main thread which doesn't lock anything so your behaviour is not predictable. This is why I added the captured variable i that grabs the value of a.i when the thread is created, and is used in the thread delegate in a safe way.

Also, always lock on a non-public instance. if you lock on A, make sure no-one sees A and gets the opportunity to lock on it.


Because the lock is always held by the main thread, as you are starting threads after acquiring lock and once you acquire there is no contention. Now the threads are free to run however they want, the threads which started by main thread aren't synchronized by any lock. Something which comes close to your expections is following (only order) count again depends on how fast and how many cores you've got. Observe b.j++ is now inside a lock.

    for (b.j = 0; b.j < 9; )
    {
        new Thread(() => { lock (b) { Console.Write(b.j); b.j++; } }).Start();
    }

Basic idea behind locking or critical section is to only allow one thing to happen, not the order, in the above modification I've locked the increment operation, that gaurantees that before next thread starts running code under lock, current thread has to finish running all the code under its acquired lock, before it releases the lock.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜