Dangers of using lazy vars while multithreading

APRIL 14, 2020

I recently was working on a networking layer and was having some threading issues. I was sending out 10 requests and sometimes I would get 10 responses, sometimes I would get 2, and sometimes the whole app would crash. These were all signs of a classic multithreading issue, but I couldn’t figure out what was wrong.

After putting in some print statements I found that some variables deep in my networking layer were going nil, which was causing the weird issues I was having. I checked all the properties within my networking layer and they were all strong references, so they should not have been going nil.

Finally I realized that the networking layer itself was defined as a lazy var. I wondered “could this be because the variable mutates the first time its accessed?” To test this out I changed the property to a let constant and then everything worked as expected. This lead me to look at the documentation of lazy vars. In the “Lazy Stored Properties” section of the Swift Programming Guide it states: “If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.”

To reproduce this issue I created created a very simple example class that completes an async task. It’s simply a method that can be called to perform an async task which internally calls another method to shunt the response back to the main thread. I also added a check to print out if self is nil.

Simple example class example

In order to demonstrate the issue I also created an example view controller that contains a lazy property and let constant of this example class. I then call each 10 times concurrently, which simulates a multithreaded environment.

Simple example view controller

I got inconsistent results similar to what I was seeing with my networking layer. A lot of times I would I get 10 responses from both the lazy property and the constant property.

However, a lot of times I would get less than 10 responses from the lazy property. In this case the lazy property only got 8 responses while the constant property got 10 responses.

Lazy property only returning 8 times

“Self is nil” gets printed twice which is why two responses never come back.

Even worse I would sometimes get a crash.

Running into these threading issues makes sense because lazy vars are mutable for a small amount of time. They mutate from nil to a concrete value on first access. So when we access a lazy var, for the first time, within a multithreaded environment things get out of whack. Before a lazy var is initialized (after being accessed the first time) some threads can get a nil value and other threads may get a concrete value.

TLDR;

Be careful accessing lazy vars in a multithreaded environment. If a lazy var is accessed for the first time (before initialization) from multiple threads it can cause crashes and undefined behavior.