General speaking, file system will lock the file when an application is accessing it. If another application wana access this file, it must wait for the lock to be released. In multithreaded programming, we face the same situation. However, to make it easy, .NET provide synchronization object to help us to do this task. There are four types of resources that we need to pay attendtion:
-System resources, like communication ports
-Resources shared by multiple process, like file handles
-The resources of a single application domain accessed by multiple threads, like global, static, and instance fields
-Objects instances that are accessed by multiple threads.
Monitor Class: is applied to lock objects. In C#, using keword
lock to specify the object that you want to monitor.
1
public
void
Subtract()
2
{
3
lock
(
this
)
4
{
5
result
=
value1
-
value2;
6
Thread.Sleep(
1000
);
7
Console.WriteLine(
"
Subtract:
"
+
result);
8
}
9
}
When a Thread.Start(Subtract()) is called, other functions cannot access value1, value2, and result due to Subtract() is accessing these varialbes. In other words, this lock behavior prevents result value from being overwritten. Nevertheless, Monitor class does not lock value type, but only lock the reference type. Therefore, Monitor locks the object to which Subtract() belongs, not the result variables itself.
However, Monitor class does not separate read and write lock. Multiple threads might need to read value simultaneously. If using Monitor class, only one thread can read a file at a time, another one has to wait.
ReaderWriterLock, allowes more than one threads read file at same time. And this would be stopped if a thread begin to write to this file.
1
class
MemFile
2
{
3
string
file
=
"
Hello, world!
"
;
4
ReaderWriterLock rwl
=
new
ReaderWriterLock();
5
public
void
ReadFile()
6
{
7
//
Allow thread to continue only if no other thread
8
//
has a write lock
9
rwl.AcquireReaderLock(
10000
);
10
for
(
int
i
=
1
; i
<=
3
; i
++
)
11
{
12
Console.WriteLine(file);
13
Thread.Sleep(
1000
);
14
}
15
rwl.ReleaseReaderLock();
16
}
17
public
void
WriteFile()
18
{
19
//
Allow thread to continue only if no other thread
20
//
has a read or write lock
21
rwl.AcquireWriterLock(
10000
);
22
file
+=
"
It's a nice day!
"
;
23
rwl.ReleaseWriterLock();
24
}
25
}
Interlocked Performing atomic operations (are static methods) in thread-safe way, which is an alternative to locking access to a resource.
1
int
num
=
0
;
2
//
num += 1
3
Interlocked.Increment(
ref
num);
4
//
num -=1;
5
Interlocked.Decrement(
ref
num);
6
//
num += val2
7
Interlocked.Add(
ref
num,
10
);
8
//
Set the value of an object: num = 35
9
Interlocked.Exchange(
ref
num,
35
);
10
//
if num == 75 then num = 35
11
Interlocked.CompareExchange(
ref
num,
75
,
35
);
12
//
This is equivalent to reading a variable.
13
Interlocked.Read(
ref
num);
Increment an Integer in multithread application (2 threads) can cause some error if Interlocked.Increment() is not applied:
- Incrementing an Integer has 2 steps: 1, read the original value; 2, replace the value with newly incremented value.
- When Thread1 read the original value (i.e. 10) and then is interrupted by Thread2, Thread2 read the same original value (10) and replace with 11 (add 1).
-
When Thread2 finished, Thread1 still process the original value as it was before Thread2 updated it, so it is still 10, and then rewrite the value with 11.
- Therefore, two threads cause only 1 increment, which is actually expected to cause 2 increments.
Using Interlocked.Increment(ref num) can avoid this error. Because it does not allow another thread to interrupt the increment operation.