Thread-Safe List<T> Property

Thread-safe ListT property

If you are targetting .Net 4 there are a few options in System.Collections.Concurrent Namespace

You could use ConcurrentBag<T> in this case instead of List<T>

Is this ListT property thread-safe?

Nope, it's not thread-safe. Look at the following code:

static MyClass<int> sharedInstance = ...;

// Create a list
var list = new List<int>();

// Share the list
sharedInstance.MyT = list;

// list is now shared, this call is not thread-safe.
list.Add(5);

The problem is that you allow consumers to have a reference to an internal data structure. You can solve this problem as follows:

private List<T> MyT
{
get
{
lock (_syncLock)
return _T.ToList<T>();
}
set
{
var copy = value.ToList();

lock (_syncLock)
_T = copy;
}
}

how do I capture a property in thread-safe way

Yes, using the captured lock ensures that the observer’s closure is synchronized with other tasks using the same lock. You can use this capturing pattern because lock happens to be a constant.

That raises the more fundamental problem, namely the capturing of the b reference, which is not constant. That means that if you call changeB at some intervening point in time, your notification block will still reference the original captured B, not the new one.

So, you really want to fall back to the weak self pattern if you want this to reference the current B:

class A {
func listenToB() {
NotificationCenter.default.addObserver(forName: Notification.Name("B"), object: nil, queue: nil) { [weak self] notification in
guard let self = self else { return }

let k = notification.userInfo!["k"] as! Int
self.lock.lock()
defer { self.lock.unlock() }
print(self.b.test(k))
}
}
}

Is this c# usage of List thread safe?

No, it's not thread-safe. You might be looking for the ConcurrentBag<T> class, a thread-safe unordered collection. Some more info and other thread-safe collections are available at MSDN's Thread-Safe Collections documentation. E.g.

static List<int> Calculate(List<string[]> numbers)
{
var sums = new ConcurrentBag<int>();

Parallel.ForEach(numbers,
(nums) =>
{
int sum = 0;
for (int i = 0; i < nums.Length; i++)
sum += Convert.ToInt32( nums[i]);

sums.Add(sum);
});

var sorted = sums.OrderBy(x => x).ToList();
return sorted;
}

Thread safe access of ListT properties

The lock is useless that way. You will have to use a thread-safe collection such as Will suggested, or if you don't need write access you can expose only a read only version of your list like so:

public ReadOnlyCollection<Character> Characters {
get {
lock (locker) { return this.characters.AsReadOnly(); }
}
}

These collections cannot be modified, so if your Character type is immutable, you don't have any synchronization issues. If Character is mutable, you have again a problem, but you would have that problem even with a thread-safe collection. I hope you're aware of that. You can also expose the property returning an IList<Character>, but usually I find it better to tell the caller that the object is read only.

If you need write access, you could also do that by providing the appropriate methods at the scope of the CharacterManager and synchronize them. Jesse has written a nice example on how to do this.

EDIT: SyncRoot is not present on ICollection<T>.

Using static get only property thread safe?

Is there a chance that while a thread is referencing MyFoo.Foo another thread that reference it will get an uncompleted or un-initialized data back b/c the InitFoo() is not complete yet?

No. Type initialization is thread-safe:

  • No other threads get to use your type while it's being initialized by another thread
  • All writes to memory performed by the initialization thread are made visible to other threads when the initialization has been performed

There's one wrinkle which is that if the same thread that's initializing MyFoo ends up reading MyFoo._foo before it's finished initializing, that will cause a problem. That can be particularly awkward to diagnose if there are types that depend on each other for initialization in a cycle.

Here's an example, with two type initializers that each use a value from the other. They both have static constructors to make the behavior deterministic. (The rules for when types are initialized depend on whether or not they have static constructors.)

using System;

public class Program
{
public static void Main(string[] args)
{
// Determine which type to initialize first based on whether there
// are any command line arguemnts.
if (args.Length > 0)
{
Class2.DoNothing();
}
Console.WriteLine($"Class1.Value1: {Class1.Value1}");
Console.WriteLine($"Class2.Value2: {Class2.Value2}");
}
}

public class Class1
{
public static readonly string Value1 =
$"When initializing Class1.Value1, Class2.Value2={Class2.Value2}";

static Class1() {}
}

public class Class2
{
public static readonly string Value2 =
$"When initializing Class2.Value2, Class2.Value2={Class1.Value1}";

static Class2() {}

public static void DoNothing() {}
}

Running this without any command line arguments, Class1 starts initializing first, which in turn initializes Class2:

Class1.Value1: When initializing Class1.Value1, Class2.Value2=When initializing Class2.Value2, Class2.Value2=
Class2.Value2: When initializing Class2.Value2, Class2.Value2=

With any command line argument, we initialize Class2 first, which in turn initializes Class1:

Class1.Value1: When initializing Class1.Value1, Class2.Value2=
Class2.Value2: When initializing Class2.Value2, Class2.Value2=When initializing Class1.Value1, Class2.Value2=

Is updating a property for an object in a Parallel.ForEach thread safe?

Firstly, Strings are immutable, and references are atomic, so that property is thread safe, that's not to say it's immune from stale values and data races - though that doesn't seem to be the case here

Secondly, you are calling an IO workload in parallel.ForEach which is not optimal. Meaning, you are blocking and tying up thread pool threads which could be efficiently offloaded to IO Completion ports. You are likely just better off letting the API call be async, and using Task.WhenAll

Example

public async Task<Customer> CallAPIToReturnCustomerNameAsync(int customerId) 
{
///await someThingAsyncHere();
}

...

async Task Process(Customer customer)
{
customer.CustomerName = await CallAPIToReturnCustomerNameAsync(customer.CustomerId);
}

var tasks = namelessCustomers.Select(Process);
await Task.WhenAll(tasks);

Creating Thread Safe List using Reader Writer Lock

Implementing a custom List<T> that encapsulates thread-safety is rarely worth the effort. You're likely best off just using lock whenever you access the List<T>.

But being in a performance intensive industry myself there have been cases where this becomes a bottleneck. The main drawback to lock is the possibility of context switching which is, relatively speaking, extremely expensive both in wall clock time and CPU cycles.

The best way around this is using immutability. Have all readers access an immutable list and writers "update" it using Interlocked operations to replace it with a new instance. This is a lock-free design that makes reads free of synchronization and writes lock-free (eliminates context switching).

I will stress that in almost all cases this is overkill and I wouldn't even consider going down this path unless you're positive you need to and you understand the drawbacks. A couple of the obvious ones are readers getting point-in-time snapshots and wasting memory creating copies.

ImmutableList from Microsoft.Bcl.Immutable is also worth a look. It's entirely thread-safe.



Related Topics



Leave a reply



Submit