Reputation: 4582
I'm currently working on a multithreaded project. Mainly for learning purposes.
My part in this project is to write a server application.
But I got to a point where this whole locking of variables is a bit confusing because I don't know exactly how it works and when/where I have to use it.
Lets say I have a class ClientHandler
that contains a List<Client> clientList
.
ClientHandler
has a property ClientList
that returns clientList
:
private List<Client> clientList;
public List<Client> ClientList
{
get { lock (lockObject) return clientList; }
set { lock (lockObject) clientList = value; }
}
NetworkHandler
runs a second Thread
that works with this List<>
.
In this network Thread
the retrieved List<>
is locked with another object
while in use.
My question is how this locking works.
If I lock
this List<>
in the network Thread
(with a different object
than ClientHandler
) is it thread-safe? So in general is it true that if you lock
a variable with whatever object
, is it locked for everyone else trying to access it?
I just want to be sure that the List<>
cannot be altered while it is processed in another Thread
.
Upvotes: 1
Views: 173
Reputation: 11252
I am assuming that you want the List itself to be passed around and manipulated on separate threads via Add/Remove/Clear/foreach methods, but not the objects themselves.
If this is the case then the List itself needs to implement an internal locking mechanism on each operation (Add, Remove, etc). The List creates the lockObject
, not you.
Obviously a List<T>
can't do this, so you need to either derive from it or implement your own IList/ICollection class, or you can just use a collection from the System.Collections.Concurrent
namespace which is designed for this purpose already, such as ConcurrentDictionary
.
If you want to make the objects inside the collection accessible by multiple threads then you have to make those objects thread safe... which is a completely separate step.
For a brief explanation of how the lock keyword works:
When you lock an object by specifying lock (someObject) { }
, everything inside the code block will only be executed when every other instance of a lock on that same object is not executing.
At the start of the lock code block it sets a thread-safe flag somewhere that says "I'm reserving this object", and at the end of the lock block it says "I am no longer reserving this object." At the start of the lock block if it tries to lock an object but that object is already locked then it will wait indefinitely until it can successfully obtain a lock, blocking the calling thread in the process.
In your example you are using an inline code block:
get { lock (lockObject) return clientList; }
Which is equivalent to:
get
{
lock (lockObject)
{ // Lock starts here
return clientList;
} // Lock ends here
}
So if you access that property then the object is unlocked as soon as its given to the caller of the property. Then the caller can go ahead and call the Add() method on that List and internally it will access the inner collection with no locking mechanism, so it won't be thread safe.
Instead, the List needs to call lock everytime it accesses the internal fields, such as the _innerList
field, like so:
public void Add(object item)
{
lock (_innerList)
{
_innerList.Add(item);
}
}
Upvotes: 3