0

I'm practicing some C# threading and encountered this situation:

lock (locker) { if (x != 0) y /= x; }

Is there any chance that a context switch will happen after the if and before the y /= x;? If so, a thread can change the value of x to 0 and then after another context switch will end up diving by zero, can it happen? What are the fixes for this problem?

Thank you.

Update: x and y are both properties of a class.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Dorni
  • 699
  • 1
  • 5
  • 13
  • What exactly do you mean by context switch here? The `lock` statement ensures that only a single thread can enter at a time – Camilo Terevinto Sep 26 '20 at 11:07
  • Maybe I need to clarify that. Assuming x and y are both public properties of a class – Dorni Sep 26 '20 at 11:12
  • Now this got more complicated. Can you post the full class (including properties/fields/methods)? Otherwise we cannot answer your question – Camilo Terevinto Sep 26 '20 at 11:15
  • 3
    A context-switch can happen anytime, anywhere. It isn't needed to cause trouble, processors have more than one core these days so two threads could be accessing the variables at the exact same time. `lock` protects against both mishaps. – Hans Passant Sep 26 '20 at 11:45

1 Answers1

1

A context switch can happen anywhere and anytime, because it is scheduled by the operating system and not by the .NET platform. So yes, it is possible that a division by zero may occur, provided that the class's locking scheme is inadequately implemented, and does not offer full protection for its internal state. For example if the class exposes the x as an unprotected public field, then nothing can prevent the consumers of the class from corrupting (inadvertently) its internal state, by modifying the x at an unfortunate moment.

public int x; // This is not thread safe

Here is how you could allow public access to the x field in a thread-safe manner:

private int x;

public int X
{
    get { lock (locker) return x; }
    set { lock (locker) x = value; }
}

Locking on the set ensures that the x will not be changed while the thread that does the calculation is interrupted due to a context switch. Any other thread willing to modify the x will have to wait for the first thread to resume and release the lock, before being allowed to proceed with the modification.

Locking on the get is not strictly required for preventing the division by zero exception, and at first sight it may seem redundant. Nonetheless it is a good practice to follow, because eliminating lock-free access to shared state makes it much easier to prove the correctness of a multithreaded application. It ensures visibility (in that respect it is an alternative to the volatile keyword), and it may become very important in case later the type of x is changed (due to business requirements) from int to Decimal (or any other struct larger that IntPtr.Size). In that case locking will prevent torn reads of the field.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • While the initial statement (that a context switch can happen anywhere and anytime) is correct, the conclusion (at least basing of the example provided by OP) is wrong. The `lock` statement is able to prevent intervention of another threads to the execution or protected block as other threads which are going to execute this block of coded will have `Wait` status by waiting on a lock-related synchronization object and scheduler won't make any of them running if the object isn't released, which means that the context switch won't happen for them. – Dmytro Mukalov Sep 27 '20 at 09:40
  • @DmytroMukalov my conclusion is that a division by zero is possible, unless **every** access to the `x` field is protected by the same `locker`. Do you think otherwise? – Theodor Zoulias Sep 27 '20 at 10:00
  • Your protection of field with `lock` is absolutely redundant and useless, division be zero isn't possible in the code presented by OP. – Dmytro Mukalov Sep 27 '20 at 10:20
  • @DmytroMukalov you mean that division be zero is impossible, even if the `x` field is modified by another thread without synchronization, like this: `Task.Run(() => { while (true) x = 0; });`? – Theodor Zoulias Sep 27 '20 at 10:37
  • It's possible unless you do something like that without `lock` protection which refers same synchronization object. But it's redundant to do it on the fields level. – Dmytro Mukalov Sep 27 '20 at 10:54
  • @DmytroMukalov you mean that adding synchronization on the fields level will not prevent consumers of the class from corrupting (inadvertently) its internal state, causing a division by zero exception? – Theodor Zoulias Sep 27 '20 at 11:00
  • Does "redundant" mean to do something opposite to intended? But I should admit that I see what you mean by potentially poorly implemented locking which is kind of unknown at the moment. – Dmytro Mukalov Sep 27 '20 at 11:05
  • @DmytroMukalov thanks for admitting that [my friend](https://chat.stackoverflow.com/rooms/214037/discussion-on-answer-by-theodor-zoulias-is-returning-a-task-enough-to-make-the-m). – Theodor Zoulias Sep 27 '20 at 11:16