-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy paththread-safety-in-swift.html
498 lines (425 loc) · 32.4 KB
/
thread-safety-in-swift.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. In this article, I'll share my favorite methods of ensuring thread safety, as well as analyzing the performance of the different mechanisms.">
<meta name="title" content="Thread Safety in Swift">
<meta name="url" content="https://swiftrocks.com/thread-safety-in-swift">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Thread Safety in Swift"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. In this article, I'll share my favorite methods of ensuring thread safety, as well as analyzing the performance of the different mechanisms."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/thread-safety-in-swift"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Thread Safety in Swift"/>
<meta name="twitter:description" content="Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. In this article, I'll share my favorite methods of ensuring thread safety, as well as analyzing the performance of the different mechanisms."/>
<meta name="twitter:site" content="@rockbruno_"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="images/favicon/iconsmall2.png" sizes="32x32" />
<link rel="apple-touch-icon" href="images/favicon/iconsmall2.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<link rel="canonical" href="https://swiftrocks.com/thread-safety-in-swift"/>
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism4.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style48.css">
<link rel="stylesheet" type="text/css" href="css/sponsor4.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/thread-safety-in-swift"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2021-05-11T14:00:00+02:00",
"dateModified": "2024-06-17T11:30:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb.jpg"
}
},
"headline": "Thread Safety in Swift",
"abstract": "Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. In this article, I'll share my favorite methods of ensuring thread safety, as well as analyzing the performance of the different mechanisms."
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2light.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Thread Safety in Swift-->
<!--WRITEIT_POST_HTML_NAME=thread-safety-in-swift-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. In this article, I'll share my favorite methods of ensuring thread safety, as well as analyzing the performance of the different mechanisms.-->
<!--DateFormat example: 2020-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2024-06-17T11:30:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2021-05-11T14:00:00+02:00-->
<title>Thread Safety in Swift</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Thread Safety in Swift</h1>
</div>
<div class="post-info">
<div class="post-info-text">
Published on 11 May 2021
</div>
</div>
<div class="post-image">
<img src="https://i.imgur.com/2PDjXPQ.png" alt="Lock times">
</div>
<p>Concurrency is the entry point for the most complicated and bizarre bugs a programmer will ever experience. Because we at the application level have no real control over the threads and the hardware, there's no perfect way of creating unit tests that guarantee your systems will behave correctly when used by multiple threads at the same time.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>You can, however, make some very educated guesses. In this article, we'll take a look at what thread safety is, which tools iOS provides to help us achieve it, and how they compare in terms of performance.</p>
<h2>What is Thread Safety?</h2>
<p>I personally define thread safety as a system's ability to ensure "correctness" when multiple threads attempt to use it at the same time. Look at a specific class you have that contains logic that can be accessed by background threads and ask yourself: Is it possible for <b>any</b> two lines of code in this class to run in parallel? If so, would that be fine or would that be <i>really</i> bad?</p>
<p>One thing I've noticed is that while developers generally don't have trouble understanding the concept of thread safety, a lot of people have trouble applying it in practice. Consider the following example of a class that stores a person's full name:</p>
<pre><code>final class Name {
private(set) var firstName: String = ""
private(set) var lastName: String = ""
func setFullName(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}</code></pre>
<p>Try asking yourself the same question as before. What would happen if two threads called <code>setFullName</code> at the same time?</code> Would that work fine or would that be really bad?</p>
<p>The answer is the latter. Because we're not synchronizing the threads' access to this class's state, you could have the following scenario happen:</p>
<blockquote>
<p>Thread 1: Call <code>setFullName("Bruno", "Rocha")</code></p>
<p>Thread 2: Call <code>setFullName("Onurb", "Ahcor")</code></p>
<p>Thread 1: Sets <code>firstName</code> to "Bruno"</p>
<p>Thread 2: Sets <code>firstName</code> to "Onurb"</p>
<p>Thread 2 <b>(Again)</b>: Sets <code>lastName</code> to "Ahcor"</p>
<p>Thread 1: Sets <code>lastName</code> to "Rocha"</p>
<p>Final name: "Onurb Rocha". That's not right...</p>
</blockquote>
<p>This is called a <b>race condition</b>, and it's the least-worst scenario in this case. In reality, what would probably happen is that having two threads attempt to access the strings' memory addresses at the same time would trigger a EXC_BAD_ACCESS exception and crash your app.</p>
<p>In short, this means that the <code>Name</code> class is not thread-safe. To fix the above race condition, we need to <b>synchronize how threads get to access and modify the state of this class</b>. If we make it so that <i>Thread 2</i> cannot start running <code>setFullName</code> until <i>Thread 1</i> finishes doing so, the scenario above would become impossible.</p>
<p>In practice, many developers have trouble getting this right because they <b>confuse atomicity with thread safety.</b> Consider the following attempt to fix the race condition:</p>
<pre><code>var dontLetSomeoneElseInPlease = false
func setFullName(firstName: String, lastName: String) {
guard !dontLetSomeoneElseInPlease else {
return
}
dontLetSomeoneElseInPlease = true
self.firstName = firstName
self.lastName = lastName
dontLetSomeoneElseInPlease = false
}</code></pre>
<p>Many developers would look at this and think it solves the problem, while in reality, it achieves quite literally nothing. First of all, <b>booleans in Swift are not atomic like in Obj-C</b>, meaning that this code would give you the exact same memory corruption crash you'd have if you didn't have this logic in place. You need to use OS-level synchronization APIs, which we'll mention further below in the article in detail.</p>
<p>Second of all, even if you <b>did</b> create your own custom <code>AtomicBool</code> class, you'd still not be solving the race condition. While making <code>dontLetSomeoneElseInPlease</code> atomic would result in the boolean <i>itself</i> being thread-safe, that doesn't mean that the <code>Name</code> class <i>as a whole</i> is. What's difficult to grasp here is that thread safety is <b>relative</b>; while something might be thread-safe in relation to itself, it might not be in relation to something else. When evaluating thread safety from the point of view of <code>Name</code>, <code>setFullName</code> is still unsafe because it's still possible for multiple threads to go past the guard check at the same time and cause the same race condition scenario from before.</p>
<p>To prevent a race condition in the state of the <code>Name</code> class, you need to prevent the <b>entire</b> <code>setFullName</code> logic from running in parallel. There are many different APIs which you can use to achieve this (iOS 15's async/await set of features being the most popular one as of writing), but here's one example that uses basic synchonization locks:</p>
<pre><code>var stateLock = OSAllocatedUnfairLock()
func setFullName(firstName: String, lastName: String) {
stateLock.lock()
self.firstName = firstName
self.lastName = lastName
stateLock.unlock()
}</code></pre>
<p>In theoretical terms, what we did by wrapping the logic around calls to <code>lock()</code> and <code>unlock()</code> was to establish a <b>critical region</b> within <code>setFullName</code> which only one thread can access at any given time (a guarantee made by the <code>OSAllocatedUnfairLock</code> API in this case). The logic within <code>setFullName</code> is now thread-safe.</p>
<p>Does this mean that the <code>Name</code> class itself is now thread-safe? <b>It depends on the point of view.</b> While the <code>setFullName</code> method itself is safe from race conditions, we still technically could have a race condition if some external object attempted to read the user's name in parallel with a new name being written. This is why the most important thing for you to keep in mind is the <b>relativity</b> of this problem: While you could say that <code>Name</code> is technically thread-safe in relation to itself, it could very well be the case that whatever class that would be using this in a real application could be doing so in a way that is not. Even the reverse can happen: Although the strings in this example are technically not thread-safe themselves, they are if you consider that they cannot be modified outside <code>setFullName</code>. To fix thread safety issues in the real world, you'll need to first rationalize the problem this way to determine what exactly needs to be made thread-safe in order to fix the problem.</p>
<h2>Other concurrency problems</h2>
<p>In the example above we talked about <b>race conditions</b> specifically, but in practice there are many different types of problems that you might encounter when working with thread safety and concurrency in general. We won't go into deep details of all of them, but here's a summary that covers the most common ones:</p>
<ul>
<li><b>Data Race:</b> Two threads accessing shared data at the same time, leading to unpredictable results</li>
<li><b>Race Condition:</b> Failing at synchronize the execution of two or more threads, leading to events happening in the wrong order</li>
<li><b>Deadlock:</b> Two threads waiting on each other, meaning neither is able to proceed</li>
<li><b>Priority Inversion:</b> Low-priority task holding a resource needed by a high-priority task, causing delays in execution</li>
<li><b>Thread Explosion:</b> Excessive number of threads in the program, leading to resource exhaustion and decreased system performance</li>
<li><b>Thread Starvation:</b> Thread is unable to access a resource it needs because other threads are monopolizing it, causing delays in execution</li>
</ul>
<h2>Thread Safety costs</h2>
<p>Before going further, it's good to be aware that any form of synchronization comes with a performance hit. This is not something you can run away from if you need to run code in parallel, but you can control how bad the impact will be. Different APIs have different performance costs, so by picking the right API for your problem, you can at least make sure you're not "over paying" for its thread safety. It's very common for developers to not be aware of these performance costs and proceed to pick extremely expensive APIs for relatively simple problems <a href="https://stackoverflow.com/questions/28191079/create-thread-safe-array-in-swift">(here's one great example, look at the top two answers)</a>, so I strongly recommend you to be aware of these costs (which we'll see in the next section).</p>
<h2>Thread Safety APIs in iOS</h2>
<p>As mentioned in the example, there are many different ways in which you can achieve thread safety in Swift. The right API to use depends on the issue you're facing, so in this section we're going to cover all of them and provide some examples that shows what they're supposed to be used for.</p>
<h3>async/await</h3>
<p>If your app has a minimum target of at least iOS 15, you should probably go for async/await and ignore everything else in this article.</p>
<p>async/await doesn't solve the problem of concurrency, but it does make it a bit less problematic. By far the biggest problem with concurrency is how easy it is for you to make dangerous mistakes, and while async/await doesn’t protect you from logic mistakes / straight-up incorrect code, the way the feature works makes it <b>safe from deadlocks, thread explosion, and data races</b>, which is a major achievement for Swift as a programming language. You still need to be careful about race conditions and wrong code in general though, especially because the feature has many "gotchas" to it.</p>
<p>For more information on async/await, how to use it to implement thread safety, and what are those "gotchas" that you need to be careful about, <a href="how-async-await-works-internally-in-swift">check out my deep dive on async/await.</a></p>
<p>If you cannot use async/await, here are some of the "old school" synchronization APIs in iOS:</p>
<h3>Serial DispatchQueues</h3>
<p>Despite not being generally connected to the topic of thread safety, <code>DispatchQueues</code> can be great tools for thread safety. By creating a queue of tasks where only one task can be processed at any given time, you are indirectly introducing thread safety to the component that is using the queue.</p>
<pre><code>let queue = DispatchQueue(label: "my-queue", qos: .userInteractive)
queue.async {
// Critical region 1
}
queue.async {
// Critical region 2
}</code></pre>
<p>The greatest feature of <code>DispatchQueue</code> is how it completely manages any threading-related tasks like locking and prioritization for you. Apple advises you to never create your own <code>Thread</code> types for resource management reasons -- threads are not cheap, and they must be prioritized between each other. <code>DispatchQueues</code> handle all of that for you, and in the case of a serial queue specifically, the state of the queue itself and the execution order of the tasks will also be managed for you, making it perfect as a thread safety tool.</p>
<p>Queues, however, are only great if the tasks are meant to be completely asynchronous. If you need to synchronously wait for a task to finish before proceeding, you should probably be using one of the lower-level APIs mentioned below instead. Not only running code synchronously means that we have no use for its threading features, resulting in wasted precious resources, but the <code>DispatchQueue.sync</code> synchronous variant is also a relatively dangerous API as it cannot deal with the fact that you might <b>already</b> be inside the queue:</p>
<pre><code>func logEntered() {
queue.sync {
print("Entered!")
}
}
func logExited() {
queue.sync {
print("Exited!")
}
}
func logLifecycle() {
queue.sync {
logEntered()
print("Running!")
logExited()
}
}
logLifecycle() // Crash!</code></pre>
<p>Recursively attempting to <b>synchronously</b> enter the serial <code>DispatchQueue</code> will cause the thread to wait for <i>itself</i> to finish, which doesn't make sense and will result in the app freezing for eternity. This scenario is called a <b>deadlock</b>.</p>
<p>It's technically possible to fix this, but we will not go into details of that as it's simply not a good idea to use <code>DispatchQueues</code> for synchronous purposes. For synchronous execution, we can have better performance and more predictable safety by using an old-fashioned mutual exclusion (mutex) lock.</p>
<p>Note: When using <code>DispatchQueues</code>, make sure to follow <a href="https://gist.github.com/tclementdev/6af616354912b0347cdf6db159c37057">these guidelines from @tclementdev</a> in order to use them efficiently. <b>Misusing queues can lead to serious performance and efficiency problems in your app</b>, and it's so easy that even Apple has been moving away from it.</p>
<h2>os_unfair_lock (a.k.a OSAllocatedUnfairLock or Mutex)</h2>
<p>The <code>os_unfair_lock</code> mutex is currently the fastest lock in iOS. If your intention is to simply establish a critical region like in our original <code>Name</code> example, then this lock will get the job done with great performance.</p>
<p>The way you use this lock depends on your environment. In Swift 6 you can access this via the new <code>Mutex</code> type, but as of writing this is not yet available. Thus your second approach is to use Foundation's <a href="https://developer.apple.com/documentation/os/osallocatedunfairlock">OSAllocatedUnfairLock</a> type, but it requires your app to target iOS 16.</p>
<p>You can still use it if you target iOS 15 or below, but it requires some extra work. Luckily, said extra work has been done for you! You can copy the following <code>UnfairLock</code> abstraction to try out this lock:</p>
<pre><code>// Read http://www.russbishop.net/the-law for more information on why this is necessary
final class UnfairLock {
private var _lock: UnsafeMutablePointer<os_unfair_lock>
init() {
_lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
_lock.initialize(to: os_unfair_lock())
}
deinit {
_lock.deallocate()
}
func locked<ReturnValue>(_ f: () throws -> ReturnValue) rethrows -> ReturnValue {
os_unfair_lock_lock(_lock)
defer { os_unfair_lock_unlock(_lock) }
return try f()
}
}
let lock = UnfairLock()
lock.locked {
// Critical region
}</code></pre>
<div class="post-image">
<img src="https://i.imgur.com/VR72J4H.png" alt="Alt">
</div>
<p>It's not a surprise that it's faster than a <code>DispatchQueue</code> -- despite being low-level C code, the fact that we are not dispatching the code to an entirely different thread is saving us a lot of time.</p>
<p>One very important thing you should be aware of mutexes in iOS is that all of them are <b>unfair</b>. This means that unlike a serial <code>DispatchQueue</code>, locks like <code>os_unfair_lock</code> make no guarantees in terms of the order in which the different threads get to access it. This means that one single thread could theoretically keep acquiring the lock over and over indefinitely and leave the remaining threads waiting in a process referred to as <i>starving</i>. When evaluating locks, you should first determine whether or not this can be a problem in your case.</p>
<p>Note also that this lock specifically <i>also</i> cannot handle the recursion scenario we've shown in the <code>DispatchQueue</code> example. If your critical region is recursive, you'll need to use a <b>recursive lock</b> (shown further below).</p>
<h2>NSLock</h2>
<p>Despite also being a mutex, <code>NSLock</code> is different from <code>os_unfair_lock</code> in the sense that it's an Obj-C abstraction for a completely different locking API (<code>pthread_mutex</code>, in this case). While the functionality of locking and unlocking is the same, you might want to choose <code>NSLock</code> over <code>os_unfair_lock</code> for two reasons. The first one is that, well, unlike <code>os_unfair_lock</code>, you can actually use this API without having to abstract it.</p>
<p>But lastly and perhaps more interestingly, <code>NSLock</code> contains additional features you might find very handy. The one I like the most is the ability to set a <b>timeout</b>:</p>
<pre><code>let nslock = NSLock()
func synchronize(action: () -> Void) {
if nslock.lock(before: Date().addingTimeInterval(5)) {
action()
nslock.unlock()
} else {
print("Took to long to lock, did we deadlock?")
reportPotentialDeadlock() // Raise a non-fatal assertion to the crash reporter
action() // Continue and hope the user's session won't be corrupted
}
}</code></pre>
<p>We saw that deadlocking yourself with a <code>DispatchQueue</code> at least will eventually cause the app to crash, but that's not the case with your friendly neighborhood mutexes. If you fall into a deadlock with them, they will do nothing and leave you with a completely unresponsive app. Yikes!</p>
<p>However, in the case of <code>NSLock</code>, I actually find this to be a good thing. This is because the timeout feature allows you to be smarter and implement your own fallbacks when things don't go as planned; in the case of a potential deadlock, one thing I've done in the past with great success was to report this occurrence to our crash reporter and actually <i>allow the app to proceed</i> as an attempt to not ruin the user's experience with a crash. Note however that the reason why I could gamble on that was that what was being synchronized was purely some innocent client-side logic that doesn't get carried over to the next session. For anything serious and persistent, this would be a horrible thing to do.</p>
<div class="post-image">
<img src="https://i.imgur.com/CSNvtKc.png" alt="Alt">
</div>
<p>Despite being the same type of lock as <code>os_unfair_lock</code>, you'll find <code>NSLock</code> to be slightly slower due to the hidden cost of Obj-C's messaging system.</p>
<h2>NSRecursiveLock</h2>
<p>If your class is structured in a way where claiming a lock can cause a thread to recursively try to claim it again, you'll need to use a <b>recursive lock</b> to prevent your app from deadlocking. <code>NSRecursiveLock</code> is exactly <code>NSLock</code> but with the additional ability to handle recursion:</p>
<pre><code>let recursiveLock = NSRecursiveLock()
func synchronize(action: () -> Void) {
recursiveLock.lock()
action()
recursiveLock.unlock()
}
func logEntered() {
synchronize {
print("Entered!")
}
}
func logExited() {
synchronize {
print("Exited!")
}
}
func logLifecycle() {
synchronize {
logEntered()
print("Running!")
logExited()
}
}
logLifecycle() // No crash!</code></pre>
<p>While regular locks will cause a deadlock when recursively attempting to claim the lock in the same thread, a recursive lock allows the owner of the lock to repeatedly claim it again. Because of this additional ability, <code>NSRecursiveLock</code> is slightly slower than the normal <code>NSLock</code>.</p>
<p>Do note however that <b>recursive locks are generally seen as an anti-pattern</b> because they are often used as solutions to problems where the developer doesn't have full control over when/how the lock is acquired, which usually means the system was not designed correctly. Consider whether or not that's your case before introducing a recursive lock in your project.</p>
<div class="post-image">
<img src="https://i.imgur.com/2PDjXPQ.png" alt="Alt">
</div>
<h2>DispatchSemaphore</h2>
<p>So far we've only looked at the problem of preventing two threads from running conflicting code at the same time, but another very common thread safety issue is when you need one thread to <b>wait</b> until another thread finishes a particular task before it can continue:
<pre><code>getUserInformation {
// Done
}
// Pause the thread until the callback in getUserInformation is called
print("Did finish fetching user information! Proceeding...")</code></pre>
<p>Although this may sound very similar to a lock, you'll find that you cannot implement such a thing with them. This is because what you're looking for here is the <i>opposite</i> of a lock: instead of claiming a region and preventing other threads from doing so, we want to <b>intentionally</b> block ourselves and wait for a completely different thread to release us. This is what a <b>semaphore</b> is for:</p>
<pre><code>let semaphore = DispatchSemaphore(value: 0)
mySlowAsynchronousTask {
semaphore.signal()
}
semaphore.wait()
print("Did finish fetching user information! Proceeding...")</code></pre>
<p>The most common example of a semaphore in iOS is <code>DispatchQueue.sync</code> -- we have some code running in another thread, but we want to wait for it to finish before continuing our thread. The example here is exactly what <code>DispatchQueue.sync</code> does, except we're building the semaphore ourselves.</p>
<p><code>DispatchSemaphore</code> is generally quick and contains the same features that <code>NSLock</code> has. You can additionally use the <code>value</code> property to control the number of threads that are allowed to go through <code>wait()</code> before they're blocked and <code>signal()</code> has to be called; in this case, a value of 0 means that they will always be blocked.</p>
<h2>DispatchGroup</h2>
<p>A <code>DispatchGroup</code> is exactly like a <code>DispatchSemaphore</code>, but for <b>groups</b> of tasks. While a semaphore waits for one event, a group can wait for an infinite number of events:</p>
<pre><code>let group = DispatchGroup()
for _ in 0..<6 {
group.enter()
mySlowAsynchronousTask {
group.leave()
}
}
group.wait()
print("ALL tasks done!")</code></pre>
<p>In this case, the thread will only be unlocked when <b>all</b> 6 tasks have finished.</p>
<p>One really neat feature of <code>DispatchGroups</code> is that you have the additional ability to wait <b>asynchronously</b> by calling <code>group.notify</code>:</p>
<pre><code>group.notify(queue: .main) {
print("ALL tasks done!")
}</code></pre>
<p>This lets you be notified of the result in a <code>DispatchQueue</code> instead of blocking the thread, which can be extremely useful if you don't need the result synchronously.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Because of the group mechanism, you'll find groups to be usually slower than plain semaphores:</p>
<div class="post-image">
<img src="https://i.imgur.com/V1S7N2y.png" alt="Alt">
</div>
<p>This means you should generally use <code>DispatchSemaphore</code> if you're only waiting for a single event, but in practice, the presence of the <code>notify</code> API makes a lot of people use <code>DispatchGroups</code> for individual events as well.</p>
</div></div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism4.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>