<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Alex Shchukin on Medium]]></title>
        <description><![CDATA[Stories by Alex Shchukin on Medium]]></description>
        <link>https://medium.com/@shchukin-alex?source=rss-dac92733704c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*yxktUvWTvGdNqUxppkK61A@2x.jpeg</url>
            <title>Stories by Alex Shchukin on Medium</title>
            <link>https://medium.com/@shchukin-alex?source=rss-dac92733704c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 06 May 2026 12:30:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@shchukin-alex/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Lock-free in Swift: ABA Problem]]></title>
            <link>https://shchukin-alex.medium.com/lock-free-in-swift-aba-problem-4d68622164da?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d68622164da</guid>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Mon, 24 Jun 2024 14:55:18 GMT</pubDate>
            <atom:updated>2024-06-24T14:55:18.056Z</atom:updated>
            <content:encoded><![CDATA[<p>In <a href="https://medium.com/@shchukin-alex/lock-free-in-swift-memory-model-and-petersons-algorithm-f070581c2d75">the previous article</a>, we talked about using atomic primitives. Today, we will cover one of the main challenges in lock-free algorithms: the ABA problem. This issue is hard to detect, and its solutions are often not ideal and can seem tricky.</p><p>We have already learned that the loop with compareAndSwap is a popular construction in lock-free algorithms, but this comes with a common issue known as the ABA problem. Since most of these algorithms are based on a loop with the compareAndSwap operation, which compares the expected value with the value at a specific address, the ABA problem can occur when multiple threads work with the shared address simultaneously. Let’s consider a simple example, one thread performs a compareAndSwap on a memory address M that contains a memory pointer A that refers to data C, while another thread changes the data in the following manner:</p><pre>M = A -&gt; C<br>M = B -&gt; D<br>M = A -&gt; E</pre><p>The issue here is that the same pointer A can refer to the completely different data E which is not equal initial C. But for compareAndSwap in the first thread, it doesn’t make a difference, it checks that M contains expected A, and, as a result, returns true, potentially breaking the consistency of the data structure.</p><p>For a better understanding of the ABA problem, let’s check an example of a lock-free structure that intentionally contains a flaw. This is a lock-free stack based on a while loop and compareAndSwap. It’s called ABAStack because it has a vulnerability in its design which makes the ABA problem possible.</p><p>The stack has an internal type Node that represents an element in the stack. Each Node has a reference to the next element and a generic value. The stack also holds a reference to the top element, which points to nil when it is initialized. All the references have ManagedAtomic type, allowing atomic operations on them. “Managed” in this context means that Automatic Reference Counting (ARC) will handle memory management, so we don’t need to deallocate memory manually. Additionally, we need to make Node conform to AtomicReference so an instance of the type can be held as ManagedAtomic.</p><pre>final class ABAStack&lt;T&gt; {<br>  <br>  final class Node&lt;T&gt;: AtomicReference {<br>    var value: T<br>    var next: ManagedAtomic&lt;Node?&gt;<br>    <br>    init(next: Node?, value: T) {<br>      self.next = ManagedAtomic(next)<br>      self.value = value<br>    }<br>  }<br>  <br>  private var top: ManagedAtomic&lt;Node&lt;T&gt;?&gt; = ManagedAtomic(nil)<br>}</pre><p>Here, we implement the push method. This is done using a while-loop with compareAndSwap that keeps repeating until the value is successfully set. This approach can introduce overhead compared to traditional algorithms, and in some cases, lock-free algorithms can even be slower. Another common pattern is to use a local reference for the variable within the scope of the loop. This ensures that we work with the same value through the entire iteration. In the code snippet below, we create a new node with a passed parameter. Then, we try to set it as the top element using compareExchange until it succeeds.</p><pre>func push(value: T) {<br>  let newNode = Node(next: nil, value: value)<br>  while(true) {<br>    let topNode = top.load(ordering: .relaxed)<br>    newNode.next = ManagedAtomic(topNode)<br>    <br>    if top.compareExchange(expected: topNode, desired: newNode, ordering: .releasing).exchanged {<br>      return<br>    }<br>  }<br>}</pre><p>In the push method, we use releasing ordering in compareExchange to ensure that after the operation finishes, subsequent operations will use the most recent value. In other words, we are pushing the store buffer to store the value immediately in memory. You can read more about the store buffer in <a href="https://medium.com/@shchukin-alex/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the first lock-free article</a>. To get the topNode, we can use relaxed ordering because if it doesn’t have the most recent value, it will fail at the compareExchange step and then go through another iteration in the while(true) loop.</p><p>The pop method contains a similar while loop. Initially, we get a reference to the top element of the stack. Then, we try to exchange the top element with its next element until it succeeds. In the case where the top node is nil, we return nil as well.</p><pre>func pop() -&gt; T? {<br>  while(true) {<br>    guard let topNode = top.load(ordering: .relaxed) else { return nil }<br>    let nextNode = topNode.next.load(ordering: .relaxed)<br><br>    if top.compareExchange(expected: topNode, desired: nextNode, ordering: .acquiring).exchanged {<br>      return topNode.value<br>    }<br>  }<br>}</pre><p>We use acquiring ordering in compareExchange because we want the top value not to be rearranged with the previous values. In other, we are emptying the invalidation queue to retrieve it directly from the memory. Similar to the push method, we use relaxed ordering to obtain the topNode. However, in the case of pop, if top is nil (indicating an empty stack), the method will return nil. This scenario is not covered by the while(true) loop in terms of reorderings, but since it’s the initial state and there are no previous values for top, we are free to use relaxed ordering.</p><p>It’s worth to mention that determining the appropriate (or even optimal) memory ordering requires careful analysis and can lead to errors that are difficult to reproduce.</p><p>Before attempting to reproduce the ABA problem, let’s discuss an important detail about memory allocation. Memory allocation can reuse an address that was previously allocated in the application. This means that if you had a reference to some data and then freed it, that address can be reused in the following memory allocation. This is important because it can be a cause of the ABA problem.</p><p>To illustrate it let’s see how can it happen with ABAStack:</p><pre>let stack = ABAStack&lt;Int&gt;()<br><br>stack.push(value: 1)<br>stack.push(value: 2)<br>stack.push(value: 3)<br><br>let group = DispatchGroup()<br><br>// Thread 1<br>group.enter()<br>DispatchQueue.global().async {<br>   _ = stack.pop()<br>   _ = stack.pop()<br>   stack.push(value: 3)<br>   group.leave()<br>}<br><br>// Thread 2<br>group.enter()<br>DispatchQueue.global().async {<br>   _ = stack.pop()<br>   group.leave()<br>}<br><br>group.wait()</pre><p>Let’s consider the following situation that can occur with the code:</p><p>1. Thread 2 begins execution and encounters a context switch on the line with top.compareExchange(expected: topNode, desired: nextNode, ordering: .releasing) within the pop method.</p><p>2. Meanwhile, Thread 1 executes stack.pop(), which returns 3, and then executes stack.pop() again, returning 2. As previously discussed, we’re using automatic memory deallocation through ARC, so let’s assume that the nodes that were withdrawn (3 and 2) have already been deallocated. Next, a push operation occurs. Inside the push method, a new node is allocated, and in this scenario, it returns the same address previously used for the node with value 3. The newly allocated node with the old address becomes the top of the stack.</p><p>3. Another context switch occurs, and we resume with Thread 2 inside the pop operation. The compareExchange operation verifies the address of the top and confirms it’s the same as before and swaps the top of the stack with its next, which is the node with value 2. With manual memory management, the next element could have even been deallocated. This situation exemplifies the ABA problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ghPbhoSVSh7IIOp2rpH_Jw.png" /></figure><p>It’s important to understand that stack.push(value: 3) could push any other value, and the ABA problem could still occur because we are comparing the addresses of the nodes. Additionally, the ABA problem can happen in more complex scenarios and combinations, which can sometimes be very difficult to reproduce and understand.</p><p>Today, we’ve learned about the ABA problem and implemented our first lock-free structure, even with the flaw. There are multiple ways of solving the ABA problem but there is no perfect solution. In the upcoming articles, we will focus on possible solutions to the ABA problem and refine the stack structure accordingly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*br6LTgHoO9_M0bh8vDrnDg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d68622164da" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lock-Free in Swift: Memory model and Peterson’s algorithm]]></title>
            <link>https://shchukin-alex.medium.com/lock-free-in-swift-memory-model-and-petersons-algorithm-f070581c2d75?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/f070581c2d75</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Thu, 28 Dec 2023 16:38:30 GMT</pubDate>
            <atom:updated>2023-12-28T16:38:30.383Z</atom:updated>
            <content:encoded><![CDATA[<p>Today we will continue to explore atomics and the lock-free topic we started in <a href="https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the previous article</a>. We will discuss the memory model that Swift inherited from C++ in its atomic package and reimplement Peterson’s algorithm in Swift.</p><h4>Memory model</h4><p>Finding the right place for the memory barrier can be difficult. If we use it too often, it might slow down the code, making it sequential. In C++, there’s a memory model developed for managing memory barriers for us, and Swift has adopted that memory model as well. The main idea is for each operation with atomic primitive, we decide a way we operate with barriers. In some cases, we don’t need barriers at all, and in others, we need some order.</p><p>Let’s explore the various types of memory ordering in the model:</p><ul><li><strong>sequentiallyConsistent</strong> — this model imposes the strongest restrictions on the processor. The instructions before the atomic operation with that type can’t be reordered after it and instructions after can’t be reordered before the operation. This ordering aligns with an intuitive code execution flow for the programmer. It employs the heaviest barriers and that impacts the performance.</li><li><strong>acquiring</strong> — combined usage of two barriers <strong>Load Load</strong> and <strong>Load Store</strong> which prevent load instructions before the barrier to be reordered with the load and store instructions after the barrier.</li><li><strong>releasing</strong> — combined usage of two barriers <strong>Store Store</strong> and <strong>Load Store</strong> which prevent store and load instructions before the barrier to be reordered with store instructions after the barrier.</li><li><strong>relaxed</strong> — it’s a model that doesn’t have any restrictions and allows the processor to reorder operation for optimal performance. In other words, it involves no memory barriers at all.</li><li><strong>acquiringAndReleasing</strong> — a combination of acquiring and releasing.</li></ul><p>By leveraging acquiring and releasing we can organize access to the resource so instructions from outside of the guarded by acquiring and releasing code cannot be moved inside and instructions inside of the code block cannot move out from it.</p><p>In the following example, we can see the initiation of an atomic variable and load and store operations on it. There is a parameter ordering that allows us to configure the memory ordering for these operations. The available options aresequentiallyConsistent, acquiring, releasing and relaxed.</p><pre>import Atomics<br><br>let test = ManagedAtomic&lt;Int&gt;(10)<br>print(test.load(ordering: .acquiring))<br>test.store(15, ordering: .releasing)<br>print(test.load(ordering: .relaxed))</pre><p>Result:</p><pre>10<br>15</pre><h4><strong>Complier reorder</strong></h4><p>Another potential source of reorderings is the compiler. To optimize binary it makes heuristic optimizations and reorders. To prevent that we can use special barriers for the compilers. But we don’t need to worry about the compile reorderings because the memory barriers described above prevent compiler reorders as well.</p><h4>Read-write-modify operations</h4><p>In <a href="https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the previous article</a>, we delved into read and write atomic operations and briefly mentioned read-write-modify (RMW) operations. As a quick recap, atomic operations cannot be divided into parts during an execution. Read-write-modify operation is another type of atomic operation based on read and write operations. It enables us to perform operations expecting possible changes of data from other threads. They are crucial components of lock-free algorithms.</p><p>Compare and swap (<strong>CAS</strong>) one of the fundamental RMW operations. It has three parameters: address (address of the variable), expected (a value that we expect to be located by the provided address), and new (a new value intended to be set to the given address). If the value located by address matches the expected value CAS sets the new value to the address and returns <strong><em>true</em></strong> otherwise it doesn’t alter anything and returns <strong><em>false</em></strong>. The pseudo-code snippet below illustrates the structure of the compareAndSwap function.</p><pre>// Atomic<br>func compareAndSwap&lt;T: Equatable&gt;(address: UnsafeMutablePointer&lt;T&gt;, expected: T, new: T) -&gt; Bool {<br>  if address.pointee == expected {<br>    address.pointee = new<br>    return true<br>  }<br>  return false<br>}</pre><p>Here, we observe its representation in atomics package and its application. The deliberately left comments above and below the atomic operations serve the purpose of clarifying where barriers can be positioned based on the insights gained from <a href="https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the previous article</a>.</p><pre>import Atomics<br><br>let test = ManagedAtomic&lt;Int&gt;(0)<br><br>// Load Load | Load Store<br>let (exchanged, original) = test.compareExchange(expected: 0, desired: 1, ordering: .acquiringAndReleasing)<br>// Load Store | Store Store<br><br>// Load Load | Load Store<br>print(test.load(ordering: .acquiring))<br>print(exchanged)<br>print(original)</pre><p>Result:</p><pre>1<br>true<br>0</pre><p>In certain scenarios, we want just to exchange the original value for the new one without any extra conditions. This operation can be accomplished using exchange function. The primary distinction between exchange and store lies in the fact that exchange allows us to employ acquiringAndReleasing ordering and we will see it in more detail shortly.</p><pre>let test = ManagedAtomic&lt;Int&gt;(10)<br>// Load Load | Load Store<br>let original = test.exchange(11, ordering: .acquiringAndReleasing)<br>// Load Store | Store Store<br><br>// Load Load | Load Store<br>print(test.load(ordering: .acquiring))<br>print(original)</pre><p>Result:</p><pre>11<br>10</pre><p>Compare and swap operation is one of the key functions used in lock-free algorithms leveraging it we can implement another widely used RMW operation known as fetch and add. This operation is a lock-free variant of the conventional increment operation.</p><p>fetchAndAdd takes two parameters: the memory address and the increment value. It returns the value before the increment. Internally, it employs compareAndSwap in the while loop to exchange the current value to the incremented one. This loop will iterate until the incremented value is successfully assigned. Let’s look at its possible implementation:</p><pre>func fetchAndAdd&lt;T: AdditiveArithmetic&gt;(address: UnsafeMutablePointer&lt;T&gt;, increment: T) -&gt; T {<br>    let current = address.pointee<br>    while(!compareAndSwap(address: address, expected: current, new: current + increment)) {  }<br><br>    return current<br>}</pre><p>That is a version of the fetchAndAdd from atomics package:</p><pre>import Atomics<br><br>let test = ManagedAtomic&lt;Int&gt;(10)<br><br>// Load Load | Load Store<br>let original = test.loadThenWrappingIncrement(by: 5, ordering: .acquiringAndReleasing)<br>// Load Store | Store Store<br><br>// Load Load | Load Store<br>print(test.load(ordering: .acquiring))<br>print(original)</pre><p>Result:</p><pre>15<br>10</pre><p>There are different implementations of compareAndSwap on the various processor architectures. In the x86 architecture, it’s implemented as a single atomic command compareAndSwap . However, on ARM architectures, it involves the usage of two atomic commands: load-linked and store-conditional. This dual-command approach introduces additional computational overhead to achieve atomicity at the processor level.</p><p>To mitigate this extra cost we can use a weak version of compareAndSwap , accordingly named weakCompareAndSwap. This variant is specifically designed for use within a while true loop. It&#39;s important to note that weakCompareAndSwap does not guarantee complete atomicity and may occasionally return <strong><em>false</em></strong> even when the data hasn&#39;t changed. In contrast, the regular compareAndSwap would return <strong><em>true</em></strong> in such cases.</p><p>In the provided code snippet, a while loop is executed until the variable test gets exchanged. The body of the while loop is empty but in more complex examples involving data structures, it typically performs specific tasks within the loop.</p><pre>let test = ManagedAtomic&lt;Int&gt;(0)<br>// Load Load | Load Store<br>while(test.weakCompareExchange(expected: 0, desired: 10, successOrdering: .acquiringAndReleasing, failureOrdering: .acquiring).exchanged) { /* Empty */ }<br>// Load Store | Store Store</pre><h4>Peterson lock</h4><p>In <a href="https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the preceding article</a>, we implemented the Peterson lock at the machine level. Now, we will replicate the same functionality using the Swift atomics library. Initially, we introduce atomic variables to manage the lock state, maintaining consistency with the variable names we used in the earlier article by using A, B, and turn:</p><pre>final class PetersonLock {<br>    enum ThreadId {<br>        case thread0<br>        case thread1<br>    }<br><br>    private var A = ManagedAtomic&lt;Int&gt;(0)<br>    private var B = ManagedAtomic&lt;Int&gt;(0)<br>    private var turn = ManagedAtomic&lt;Int&gt;(0)<br>}</pre><p>A identifies that the lock has been acquired by <strong><em>thread0</em></strong> and B serves the same purpose for <strong><em>thread1.</em></strong> The variable turn determines which thread is currently executing the lock function. Although the Peterson lock implementation involves only two threads and may not represent a real-world scenario we want to implement it since we did it in the previous article. This decision is motivated by the goal of having a clearer understanding of the Swift constructions we employed here.</p><p>To recreate the logic from the previous implementation, we’re using .relaxed ordering for all atomic operations. Since we want to directly set the memory barrier, consistent in the same way taken in <a href="https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f">the earlier article</a>. The barrier function takes the parameter acquiringAndReleasing, implying a combination of acquire (<strong>Load Load</strong> and <strong>Load Store</strong>) and release (<strong>Store Store</strong> and <strong>Load Store</strong>), aligning with our abstract code from before.</p><p>It’s important to note that after the lock completes, we set A (or B) to 1, indicating that the lock has been released. Following this, we use atomicMemoryFence(ordering: .releasing) to empty <strong><em>store buffer</em></strong> and update the memory with the latest value of A (or B). This step ensures synchronization and coherence in the memory state.</p><pre>func lock(threadId: ThreadId) {<br>    if threadId == .thread0 {<br>        // Acquire thread 0<br>        A.store(1, ordering: .relaxed)<br><br>        turn.store(1, ordering: .relaxed)<br><br>        // Load Load | Load Store<br>        // Load Store | Store Store<br>        atomicMemoryFence(ordering: .acquiringAndReleasing)<br><br>        // If the lock was acquired by thread 1 keep spinning<br>        while(B.load(ordering: .relaxed) == 1 &amp;&amp; turn.load(ordering: .relaxed) == 1) { }<br><br>        // Do some work<br><br>        // Release thread 0<br>        A.store(0, ordering: .relaxed)<br><br>        // Load Store | Store Store<br>        atomicMemoryFence(ordering: .releasing)<br>    } else {<br>        // Acquire thread 1<br>        B.store(1, ordering: .relaxed)<br><br>        turn.store(0, ordering: .relaxed)<br><br>        // Load Load | Load Store<br>        // Load Store | Store Store<br>        atomicMemoryFence(ordering: .acquiringAndReleasing)<br><br>        // If the lock was acquired by thread 0 keep spinning<br>        while(A.load(ordering: .relaxed) == 1 &amp;&amp; turn.load(ordering: .relaxed) == 0) { }<br><br>        // Do some work<br><br>        // Relese thread 1<br>        B.store(1, ordering: .relaxed)<br><br>        // Load Store | Store Store<br>        atomicMemoryFence(ordering: .releasing)<br>    }<br>}</pre><p>It was an example of bridging the previous and current articles transitioning from abstract code to an implementation in Swift. Now we can refine the code leveraging all the provided by Swift Atomics memory orderings. Additionally, we want to enhance familiarity with traditional locks by introducing two methods, lock and unlock.</p><p>In the method lock below notable changes were introduced compared to the previous example with the explicit lock. Primarily, the switch from store to exchange is noteworthy, as store doesn&#39;t support the use of acquiringAndReleasing, but exchange does.</p><p>However, there’s an additional aspect to consider: acquiringAndReleasing is applied to the turn variable, signifying it empties the <strong><em>invalidateQueue</em></strong> before and executes the <strong><em>store buffer</em></strong> after the exchange method. However, we also need acquiring to be applied to A and B as well in the while loops to ensure we get the most recent value. It’s a very intricate process to arrange orderings for the atomic operations and sometimes it’s even more comprehensible to set barriers explicitly.</p><pre>func lock(threadId: ThreadId) {<br>    if threadId == .thread0 {<br>        // Acquire thread 0<br>        A.store(1, ordering: .relaxed)<br><br>        // Load Load | Load Store<br>        _ = turn.exchange(1, ordering: .acquiringAndReleasing)<br>        // Load Store | Store Store<br><br>        // If the lock was acquired by thread 1 keep spinning<br>        // Load Load | Load Store<br>        while(B.load(ordering: .acquiring) == 1 &amp;&amp; turn.load(ordering: .relaxed) == 1) { }<br>    } else {<br>        // Acquire thread 1<br>        B.store(1, ordering: .relaxed)<br><br>        // Load Load | Load Store<br>        _ = turn.exchange(0, ordering: .acquiringAndReleasing)<br>        // Load Store | Store Store<br><br>        // If the lock was acquired by thread 0 keep spinning<br>        // Load Load | Load Store<br>        while(A.load(ordering: .acquiring) == 1 &amp;&amp; turn.load(ordering: .relaxed) == 0) { }<br>    }<br>}</pre><p>Another perspective about orderings is whether the variable is shared between threads. If it does we need possibly add some barriers but if it’s not we can keep it relaxed since it will not be read outside of the processor’s cache.</p><p>unlock switches off A or B depending on the passedthreadId. In the earlier example, we didn’t encapsulate unlock in a separate function; instead, it was embedded at the end of the lock method. There, it was used another fence to set <strong>Store Store</strong> barrier. Here, we can use the same logic by passing releasing to store:</p><pre>func unlock(threadId: ThreadId) {<br>    if threadId == .thread0 {<br>        // Release thread 0<br>        A.store(0, ordering: .releasing)<br>        // Load Store | Store Store<br>    } else {<br>        // Release thread 1<br>        B.store(0, ordering: .releasing)<br>        // Load Store | Store Store<br>    }<br>}</pre><p>Now when we have implemented lock and unlock methods we can see how they work in practice:</p><pre>func testPetersonLock() {<br>    var test = 0<br>    let lock = PetersonLock()<br><br>    DispatchQueue.global().async {<br>        lock.lock(threadId: .thread0)<br><br>        test = 1<br>        sleep(1)<br>        print(test)<br><br>        lock.unlock(threadId: .thread0)<br>    }<br><br>    DispatchQueue.global().async {<br>        lock.lock(threadId: .thread1)<br><br>        test = 2<br>        sleep(1)<br>        print(test)<br><br>        lock.unlock(threadId: .thread1)<br>    }<br>}</pre><p>Result:</p><pre>1<br>&lt;- 1 second wait -&gt;<br>2<br><br>or:<br><br>2<br>&lt;- 1 second wait -&gt;<br>1</pre><p>Depending on which thread acquires the lock first, it will print the associated number and hold the thread for one second. The second thread will wait until the first one is released. As previously mentioned, this lock is designed to function with only two threads and is not suitable for production development. However, it serves as a straightforward example to gain an understanding of atomics usage.</p><p>Today, we discussed how to use atomic operations such as load, store, and RMW operations. y revisiting Peterson’s algorithm from a prior article, we demonstrated its implementation using Swift atomics primitives. In the next article, we will delve into the ABA problem which is a very common challenge for atomic algorithms.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RJJsoMY_lCh39p2ODTSkcQ.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f070581c2d75" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Primitives in Depth: Serial Queue]]></title>
            <link>https://shchukin-alex.medium.com/gcd-primitives-in-depth-serial-queue-c255cf98cf55?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c255cf98cf55</guid>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[implementation]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Fri, 05 May 2023 14:34:11 GMT</pubDate>
            <atom:updated>2023-05-25T13:43:53.403Z</atom:updated>
            <content:encoded><![CDATA[<p>In the <a href="https://medium.com/@shchukin-alex/gcd-primitives-in-depth-part-1-4d1b06117be6">previous article</a>, we implemented DispatchSemaphore and DispatchGroup ourselves. Today, we will develop a simplified version of DispatchQueue, called SerialQueue. This solution focuses on two basic methods: sync and async. The sync method executes a task on the calling thread and awaits its completion, whereas the async method carries out the task on a background thread (though not always, but we will simplify this aspect) without blocking the calling thread. For a more comprehensive understanding of queues, please check the following <a href="https://medium.com/@shchukin-alex/gcd-queues-and-methods-f12453f529e7">link</a>.</p><p>Unlike GCD’s DispatchQueue, we will make a separate class called SerialQueue, which will only contain the serial logic, excluding any concurrent components. In the code snippet provided below, we delineate the essential variables:</p><ul><li>thread: Provides the execution environment where the async task will be performed.</li><li>condition: Organized communication between the sync and async methods.</li><li>mutex: Ensures thread safety by preventing simultaneous access to shared resources.</li><li>stop: Terminates the while loop in the background thread, concluding the thread&#39;s execution.</li></ul><pre>final class SerialQueue {<br>  private var thread: pthread_t?<br>  private var condition = pthread_cond_t()<br>  private var mutex = pthread_mutex_t()<br><br>  // Used to stop executing background thread<br>  private var stop = false<br><br>  init() {<br>    let weakSelfPointer = Unmanaged.passUnretained(self).toOpaque()<br>    <br>    _ = pthread_create(&amp;thread, nil, { (pointer: UnsafeMutableRawPointer) in<br>      let weakSelf = Unmanaged&lt;SerialQueue&gt;.fromOpaque(pointer).takeRetainedValue()<br>      weakSelf.runThread()<br>      pthread_exit(nil)<br>    }, weakSelfPointer)<br>    <br>    pthread_cond_init(&amp;condition, nil)<br>    pthread_mutex_init(&amp;mutex, nil)<br>  }<br>}</pre><p>The initialization section contains standard setup functions such as pthread_cond_init and pthread_mutex_init. It also does the creation of a background thread through pthread_create. However, we cannot directly call self.runThread() from the closure passed as a parameter to pthread_create, as the compiler would identify the closure as a pointer to a C-function and return an error. To address this issue, we will create an Unmanaged pointer to self, which will be passed as a parameter to pthread_create.</p><p>Within the closure, we use Unmanaged&lt;SerialQueue&gt; to obtain a typed reference to self. The passUnretained and takeRetainedValue methods are used to manage the retain counter, ensuring a weak reference to self in this context. This implies that we do not want to increase the reference counter. Balancing the retain counter can be challenging, so using CFGetRetainCount (during debugging) is an effective approach to make adjustments. Finally, we call pthread_exit to terminate the thread, which will be executed when runThread completes its operation, in our case duringdeinit.</p><p>The underlying principle of the serial queue implementation is based on the notion that all async tasks will be executed on the background thread (the one we just created), while sync tasks will be carried out on the calling thread. The mechanism for scheduling these tasks is through the using the condition variable.</p><p>The subsequent component to develop is the Task, which represents the actual task to be executed. It comprises the async or sync type, a closure containing the logic to be executed within the queue, and a threadId. The meaning of the threadId will be elaborated later in the discussion.</p><pre>fileprivate final class Task {<br><br>  fileprivate enum TaskType: String {<br>    case sync<br>    case async<br>  }<br>  <br>  let type: TaskType<br>  let execute: (() -&gt; ())<br>  let threadId: UInt32<br>  <br>  init(type: TaskType, threadId: UInt32, execute: @escaping (() -&gt; ())) {<br>    self.type = type<br>    self.execute = execute<br>    self.threadId = threadId<br>  }<br>}</pre><p>To maintain the order of tasks, we introduce a queue that follows to FIFO principle. Another critical aspect of this queue is its thread safety, as it will be extensively used in a multithreaded environment. We will not delve too deeply into the implementation of the queue itself, as it is pretty straightforward.</p><pre>fileprivate final class Queue {<br>  private var elements: [Task] = []<br>  private var mutex = pthread_mutex_t()<br><br>  init() {<br>    pthread_mutex_init(&amp;mutex, nil)<br>  }<br><br>  func enqueue(element: Task) {<br>    pthread_mutex_lock(&amp;mutex)<br>    elements.append(element)<br>    pthread_mutex_unlock(&amp;mutex)<br>  }<br>  <br>   func dequeue() -&gt; Task? {<br>     pthread_mutex_lock(&amp;mutex)<br>     defer {<br>       pthread_mutex_unlock(&amp;mutex)<br>     }<br>     <br>     guard !elements.isEmpty else { return nil }<br>     <br>     return elements.removeFirst()<br>   }<br>   <br>   var isEmpty: Bool {<br>     pthread_mutex_lock(&amp;mutex)<br>     defer {<br>       pthread_mutex_unlock(&amp;mutex)<br>     }<br>     return elements.isEmpty<br>   }<br>}</pre><p>So we will add the queue as a private variable to our SerialQueue implementation:</p><pre>// Used to make an order for sync and async tasks<br>private let queue = Queue()</pre><p>Now we have all the primitives we need so we can start to design the methods. Since async is much easier to implement we will start with it. It gets the threadId of the calling thread and adds the task with async type to the task queue and calls pthread_cond_broadcast to wake the background thread. Again want to emphasize that threadId will be used later on when we implement sync method.</p><pre>func async(task: @escaping () -&gt; ()) {<br>  let threadId = pthread_mach_thread_np(pthread_self())<br>  queue.enqueue(element: Task(type: .async, threadId: threadId, execute: task))<br>  pthread_cond_broadcast(&amp;condition)<br>}</pre><p>There is a valid reason for not using pthread_cond_signal in this context. The main issue with pthread_cond_signal is its lack of guarantee as to which pthread_cond_wait it will awaken. The scheduling policy ultimately determines which thread will be unblocked first. By calling pthread_getschedparam, we can obtain the scheduling policy for the thread (which is often SCHED_OTHER by default in many systems), with the order being based on a priority determined by a parameter called the nice value. Instead of going deep into this direction, we will provide a more universal solution that remains independent of the calling order. This is achieved through the usage of pthread_cond_broadcast, which unblocks all wait methods using the same condition. Also, we introduce a while loop surrounding pthread_cond_wait to prevent the continuation of execution for all waiting tasks, except for the one that satisfies the condition within the loop.</p><p>Once the async task has been added to the task queue, we need to execute tasks from the queue on the background thread. To achieve this, we create a while loop that runs indefinitely until the stop flag is set. Within this infinite loop, there is another loop that retrieves tasks from the task queue until it gets empty.</p><p>This inner task loop contains a condition that verifies the type of task; if it’s an async task, it will be executed on the background thread. We will discuss the sync component in more detail shortly. Following the task’s execution, another while loop continually calls pthread_cond_wait if the task queue is empty and the stop flag is unset. This particular scenario is the one we previously discussed, which prevents the thread from being unblocked since we use pthread_cond_broadcast throughout our implementation.</p><pre>private func runThread() {<br>  while(!stop) {<br>    while let task = queue.dequeue() {<br>      if task.type == .sync {<br>        // TODO Implement<br>      } else {<br>        task.execute()<br>      }<br>    }<br>    <br>    // Until the task queue is empty put on wait<br>    while(queue.isEmpty &amp;&amp; !stop) {<br>      pthread_cond_wait(&amp;condition, &amp;mutex)<br>    }<br>  }<br>}</pre><p>To get a better grasp of the async method’s algorithm there is a schema that depicts its process of working on both the calling thread and the background thread assuming there are no tasks executing at the moment:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*mS85QER8lWABZHzEXr9tzA.png" /><figcaption>Async method</figcaption></figure><p>The implementation is not overly complex when dealing with only async tasks in the serial queue. However, integrating both sync and async tasks introduces additional complexity to the overall logic. Let us now proceed with the sync method. Prior to diving into its implementation, we must first introduce another container that will be used within the sync method. This container, called SynchronizedDictionary, is a simple thread-safe wrapper around a Dictionary. It offers two primary operations: set, which adds a value to the dictionary, and value, which returns a boolean value based on a UInt32 key.</p><pre>fileprivate final class SynchronizedDictionary {<br>  private var storage: [UInt32: Bool] = [:]<br>  private var mutex = pthread_mutex_t()<br>  <br>  init() {<br>    pthread_mutex_init(&amp;mutex, nil)<br>  }<br><br>  func add(value: Bool, key: UInt32) {<br>    pthread_mutex_lock(&amp;mutex)<br>    storage[key] = value<br>    pthread_mutex_unlock(&amp;mutex)<br>  }<br><br>  func value(key: UInt32) -&gt; Bool? {<br>    pthread_mutex_lock(&amp;mutex)<br>    defer {<br>      pthread_mutex_unlock(&amp;mutex)<br>    }<br>    return storage[key]<br>  }<br>}</pre><p>Now we can add SynchronizedDictionary as a field to SerialQueue:</p><pre>// Used to store sync execution status per thread<br>private var syncExecutionStates = SynchronizedDictionary()</pre><p>With all the components prepared, we are now ready to implement the sync method. First, we get the calling thread&#39;s ID (which will be used later on in the background thread), create the sync task, and add it to the tasks queue. The next step involves setting the value to false in the SynchronizedDictionary for the given thread ID, meaning that the sync task is not currently executing. This process is a crucial component of the scheduling mechanism, as it ensures the proper serial execution of both sync and async tasks.</p><p>After that, we lock the execution and notify the background thread if it was set to wait. Next, we place the calling thread on hold until the background thread has finished executing the other tasks in the task queue. Once the task execution is complete, we mark the calling thread as non-executing using syncExecutionStates. Then we notify the background thread that the sync execution has finished and unlock the calling thread.</p><pre>func sync(task: @escaping () -&gt; ()) {<br>  // Storing sync task with dedicated thread id into thread safe queue for the following execution<br>  let threadId = pthread_mach_thread_np(pthread_self())<br>  queue.enqueue(element: Task(type: .sync, threadId: threadId, execute: task))<br><br>  // Mark the task is NOT executing yet for the calling thread (threadId)<br>  syncExecutionStates.set(value: false, key: threadId)<br><br>  pthread_mutex_lock(&amp;mutex)<br>  // Unblock the queue thread if it doesn’t execute any task<br>  pthread_cond_broadcast(&amp;condition)<br><br>  // Put on wait current thread if the queue thread is executing the other task<br>  while(syncExecutionStates.value(key: threadId) == false) {<br>    pthread_cond_wait(&amp;condition, &amp;mutex)<br>  }<br>  // Execute the task on the calling thread<br>  task()<br>  <br>  // Mark the task is NOT executing for the calling thread (threadId)<br>  syncExecutionStates.set(value: false, key: threadId)<br>  pthread_cond_broadcast(&amp;condition)<br>  pthread_mutex_unlock(&amp;mutex)<br>}</pre><p>We intentionally place the first part of the sync method outside of the lock for a reason. To better understand this, let&#39;s consider an example: multiple calls of sync and async are made from different threads. If we were to place pthread_mutex_lock at the beginning of the sync method, it would lock the method, causing other sync calls to wait until it gets unlocked. At the same time, an async call may occur. Since the async method does not use locks, it would immediately add the task to the task queue before the sync methods that were called earlier. This would break the order of execution, so we must be sure that the task is added to the task queue immediately after the method gets called.</p><p>The same reason applies to syncExecutionStates, which needs to be placed outside the lock because it may block the calling thread. If the background thread attempts to execute the associated sync task while the calling thread is blocked, the calling thread will miss the broadcast call and the order of execution will be compromised.</p><p>The first part of the sync logic is done, and now we will switch to the remaining part that takes place within the background thread marked with TODO. If the task type is sync, we need to notify the associated sync method that the task is ready to begin execution. In order to achieve this, we pass the threadId as a parameter when adding the task to the task queue. By using syncExecutionStates, we can determine that a task with the specified threadId is currently executing.</p><p>Next, we call pthread_cond_broadcast to wake the sync method, allowing it to execute the task on the associated calling thread. Finally, we have to wait for the task execution to complete using pthread_cond_wait.</p><pre>private func runThread() {<br>  while(!stop) {<br>    while let task = queue.dequeue() {<br>      if task.type == .sync {<br>        // Mark the task is executing for the thread id<br>        syncExecutionStates.set(value: true, key: task.threadId)<br>        pthread_cond_broadcast(&amp;condition)<br>        <br>        // Lock the queue thread while the sync task is executing on the calling thread<br>        while(syncExecutionStates.value(key: task.threadId) == true) {<br>          pthread_cond_wait(&amp;condition, &amp;mutex)<br>        }<br>        continue<br>      } else {<br>        task.execute()<br>      }<br>    }<br><br>    // Until the task queue is empty put on wait<br>    while(queue.isEmpty &amp;&amp; !stop) {<br>      pthread_cond_wait(&amp;condition, &amp;mutex)<br>    }<br>  }<br>}</pre><p>The SynchronizedDictionary determines the calling thread&#39;s ID that must be executed within the sync method. For instance, imagine multiple threads calling the sync method of the same SerialQueue instance. When the background thread retrieves a sync task, it contains the thread&#39;s ID, which allows the background thread to identify the specific thread that has called the sync task.</p><p>Using the SynchronizedDictionary, we can target the appropriate thread (by setting its threadId value to true). When the target thread wakes up after calling pthread_cond_broadcast, execution will only proceed for the marked thread. Meanwhile, the other threads will go through the while loop and return to a waiting state.</p><p>The following diagram illustrates the algorithm of the sync method, depicting the shared logic between the calling thread and the background thread under the assumption that the tasks queue is empty:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/972/1*wW_Tsxj3PFV0k0QjYcfRPQ.png" /><figcaption>Sync method</figcaption></figure><p>Indeed, the sync and async methods are designed to work together as a cohesive mechanism, with one method affecting the other. This interconnection is precisely why we extensively use the condition variable in our implementation. The condition variable enables the proper coordination and synchronization between the calling and background threads, ensuring that tasks are executed in a serialized manner.</p><p>Now we need to implement deallocation logic for SerialQueue which is not trivial either. First, we set the stop flag to true, which will terminate the main while loop in the background thread within the runThread method. Next, if the background thread is not currently executing any tasks, we wake it using pthread_cond_broadcast. That ensures that the background thread can properly exit the while loop. Then we call pthread_join to make the calling thread wait until the background thread completes its execution. This step is crucial in avoiding crashes that may result from accessing deallocated memory. Once the background thread has finished executing, we proceed to deallocate the condition and mutex variables.</p><pre>deinit {<br>  // Set stop flag to finish while loop in the background thread<br>  stop = true<br><br>  // Wake background thread if it was put on wait<br>  pthread_cond_broadcast(&amp;condition)<br><br>  // Wait until background thread finishes its execution<br>  if let thread = thread {<br>    pthread_join(thread, nil)<br>  }<br><br>  pthread_cond_destroy(&amp;condition)<br>  pthread_mutex_destroy(&amp;mutex)<br>}</pre><p>Here is a schema representing how the deinit logic work:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/840/1*euinsHTNcNm-mG1h0IEoGw.png" /></figure><p><strong># Tests</strong></p><p>We now have implemented all the pieces of the comprehensive solution. Although it may seem complex, especially when addressing numerous corner cases and sometimes catching them becomes almost impossible. Gladly there are techniques and tools to help tackle these challenges. Unit testing is one such technique. In terms of the best practice for developing software, unit tests take the key role. They play an essential role in ensuring the code’s correctness and stability. While there are various approaches to writing tests, this article will focus on applying unit tests specifically to SerialQueue.</p><p>When working with asynchronous code, we often encounter floating errors that don’t occur during every execution. To help address these errors, Xcode offers a special feature called repetitive run. This feature allows you to run the test multiple times in a row, increasing the likelihood of encountering and identifying any sporadic issues. To initiate a repetitive run, simply right-click on the test and select this option from the context menu:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_N2yBRny8yyoCS9459XlIQ.png" /></figure><p>After that, we can specify the number of repetitions, termination conditions, and other parameters:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*NfoRVHmDXMVtb71C07nIrA.png" /></figure><p>In my efforts to cover most of the behavioral scenarios for SerialQueue, I wrote various unit tests. While we will only examine one such test in this article, you can explore the other tests by following the <a href="https://github.com/shchukin-alex/MultighreadingPrimitives/blob/main/MultithreadingPrimitivesTests/SerialQueueTests.swift">link</a>. <em>I encourage you to share any potential corner cases you discover in the comments below.</em></p><p>In the test below, we use expectation multiple times, which is another tool that aids in testing asynchronous code. It operates similarly to semaphores and conditions. By using the wait function, we block the calling thread until all expectations signal completion using fulfill. As you can see, some methods are called from the main thread, while others are from the global queue. There is a result array that is expected to contain the ordered sequence [1, 2, 3, 4, 5] since we are working with a serial queue, the order should be guaranteed by the call sequence. We also choose not to lock the result since only one thread can access it at a time.</p><pre>func testFromMainAndBackgroundThreads() {<br>  let serialQueue = SerialQueue()<br>  var result: [Int] = []<br>  <br>  let expectation1 = expectation(description: “test1”)<br>  serialQueue.async {<br>    sleep(1)<br>    result.append(1)<br>    expectation1.fulfill()<br>  }<br><br>  let expectation2 = expectation(description: “test2”)<br>  serialQueue.async {<br>    sleep(1)<br>    result.append(2)<br>    expectation2.fulfill()<br>  }<br><br>  let expectation3 = expectation(description: “test3”)<br>  DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) {<br>    serialQueue.sync {<br>      sleep(1)<br>      result.append(3)<br>      expectation3.fulfill()<br>    }<br>  }<br><br>  let expectation4 = expectation(description: “test4”)<br>  DispatchQueue.global().asyncAfter(deadline: .now() + 0.4) {<br>    serialQueue.async {<br>      sleep(1)<br>      result.append(4)<br>      expectation4.fulfill()<br>    }<br>  }<br><br>  let expectation5 = expectation(description: “test5”)<br>  DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {<br>    serialQueue.sync {<br>      sleep(1)<br>      result.append(5)<br>      expectation5.fulfill()<br>    }<br>  }<br><br>  wait(for: [expectation1, expectation2, expectation3, expectation4, expectation5], timeout: 10.0)<br>  XCTAssertEqual([1, 2, 3, 4, 5], result)<br>}</pre><p>In this article, we’ve implemented a simplified version of the serial queue, which is widely used in iOS applications, to get a deeper understanding of its functionality. It is important to know that the original queue implementation uses lock-free primitives, which may provide performance advantages. However, we chose not to use them here to avoid overcomplicating the article, as even with locks, the explanation was not very easy. Additionally, please remember not to use this implementation in production, as it was created solely for educational purposes and may be unstable in certain corner cases compared to the GCD version.</p><p>To see the full implementation feel free to visit <a href="https://github.com/shchukin-alex/MultighreadingPrimitives">the GitHub repository</a>.</p><p>Check my <a href="https://twitter.com/ShchukinAleks">Twitter</a> to get the newest updates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fkEKOAc2sBIaaQTL01E3fw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c255cf98cf55" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lock-free in Swift: Barriers]]></title>
            <link>https://shchukin-alex.medium.com/lock-free-algorithms-using-swift-part-1-fundamentals-d1f8b2e6ea6f?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1f8b2e6ea6f</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[hardware]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Wed, 14 Dec 2022 15:48:57 GMT</pubDate>
            <atom:updated>2023-12-29T14:46:25.487Z</atom:updated>
            <content:encoded><![CDATA[<p>I want to start another series of articles about lock-free algorithms and how we can implement them using swift atomics framework. It’s a very complicated topic with a lot of hidden stones there. I want to mention that it’s very difficult to debug and find mistakes. Sometimes algorithms can behave very unpredictably. So it’s often recommended to use usual locks instead of lock-free algorithms but I believe studying them can help to understand how processor and memory work. We will start with the basics in this article and discuss what are atomics, memory barriers, and MESI protocol.</p><h4>Atomic operations.</h4><p>An atomic operation is an operation that cannot be split into parts while it’s executing. So basically it can be executed or not executed, there is no intermediate state. There are 3 types of atomic operations: read, write, and read-write-modify (RMW). All these operations are usually implemented as processor commands and what is important they can be different for any type of processor. For example, Intel and ARM have different instructions for the CAS command. Good thing is that the high-level atomics framework covers this issue and we can more focus on the algorithm implementation. That part will cover only read and write atomic operations. About RMW you will learn in the next part.</p><p>For now, we don’t need to dig into the swift atomics framework syntax. But we can use store (for writing) and load (for reading) as atomic operations. The interesting thing is that for modern processors atomicity is guaranteed only for <strong><em>aligned</em></strong> integral types like integers or pointers but reading unaligned data is not atomic. The compiler can guarantee correct alignment for integral types. To get a better understanding of atomic operations let’s start with the simplest mutex using only store and load for two processors. It’s called Peterson’s lock. In the implementation below you can see that we are controlling the lock through the cycle while and three variables <strong><em>A</em></strong>, <strong><em>B</em></strong>, and <strong><em>turn</em></strong>. Whenever one processor executes the critical section it set its variable <strong><em>A</em></strong> (or <strong><em>B</em></strong> in the P1 case) to 1 and another processor has to wait until the first will be done.</p><pre>Initial data:<br>A = 0, B = 0 and turn = 0<br><br>P0: <br>A = 1 // store A, 1<br>turn = 1 // store turn, 1<br>// load B<br>// load turn<br>while (B == 1 &amp;&amp; turn == 1) { // Wait }<br>// Critical section<br>// Do some work<br>// End critical section<br>A = 0 // store A, 0<br><br>P1:<br>B = 1 // store B, 1<br>turn = 0 // store turn, 0<br>// load A<br>// load turn<br>while (A == 1 &amp;&amp; turn == 0) { // Wait }<br>// Critical section<br>// Do some work<br>// End critical<br>B = 0 // store B, 0</pre><p>Unfortunately, there are some issues that can happen with that code.</p><h4>Memory consistency</h4><p>And now we are touching very important problem here. This lock will work only in <strong><em>sequential consistency</em></strong> execution. That means we expect the order of the execution to be the same as it’s written. That is how the programmer expects it to be executed but the instructions can be reordered by the processor or/and compiler. In the past, we had only 1 core processor it was easier to support sequential memory consistency but nowadays the processor is still working in that paradigm: even if it has multiple cores it operates as if it has only one core and that makes it more complicated to provide a sequential consistency.</p><p>Modern processors in terms of optimizations do reorders. They put the read operation in the beginning because the read from the memory is an expensive operation and sometimes it can be reordered in the beginning. While the write/load operation is cheaper you don’t need to wait until it ends. That’s why it’s called <strong><em>relaxed memory consistency</em></strong>.</p><p>Let’s see how the implementation of Peterson’s algorithm can be reordered:</p><pre>Initial data:<br>A = 0, B = 0 and turn = 0<br><br>P0:<br>// load B from the cycle got executed before store A<br>A = 1 // store A, 1<br>turn = 1 // store turn, 1<br>// load turn<br>while (B == 1 &amp;&amp; turn == 1) { // Wait }<br>…<br><br>P1:<br>// load A from the cycle got executed before store B<br>B = 1 // store B, 1<br>turn = 0 // store turn, 0<br>// load turn<br>while (A == 1 &amp;&amp; turn == 0) { // Wait }<br>…</pre><p>P0 and P1 got executed their load B and load A before the actual stores. That means they can execute critical sections at the same time. Gladly we can protect our code from reordering. Memory barriers prevent reorders on the processor layer. Sometimes memory barriers can be very heavy even heavier than usual mutexes. That’s why programmers should be extra careful using them. Let’s see how we can use them in our example:</p><pre>Initial data:<br>A = 0, B = 0 and turn = 0<br><br>P0:<br>A = 1 // store A, 1<br>turn = 1 // store turn, 1<br><br>memory_barrier()<br><br>// load B<br>// load turn<br>while (B == 1 &amp;&amp; turn == 1) { // Wait }<br>// Critical section<br>// Do some work<br>// End critical section<br><br>A = 0 // store A, 0<br><br>P1:<br>B = 1 // store B, 1<br>turn = 0 // store turn, 0<br><br>memory_barrier()<br><br>// load A<br>// load turn<br>while (A == 1 &amp;&amp; turn == 0) { // Wait }<br>// Critical section<br>// Do some work<br>// End critical<br>B = 0 // store B, 0</pre><p>So this pseudo-code will work as it is supposed to because we set memory barriers to avoid reorders of the instructions. I intentionally didn’t specify memory barrier commands because they are different for platforms and os. <em>The barrier in this context means sequential ordering: instructions from above the barrier command will be not mixed with the instructions below it</em>. The processor would not be able to reorder load B before store A for P0 and load A before store B for P1.</p><h4>Cache coherence</h4><p>For a better understanding of why we need the memory barriers and how they work we should look into the hardware level and see what’s going on there. In modern architecture, processors use their own caches (actually multiple caches per processor) to avoid working with the memory directly because it’s a relatively expensive operation time-wise. But that causes a problem since each processor has its own local data on its cache so the data can differ from the others and the shared memory. To understand that let’s see an example:</p><pre>There are two processors P0 and P1 <br>Each has its own cache C0 and C1 <br>There is a shared memory M which they can access<br>Memory M contains value A = 0<br><br>Step 1<br>P0 loads A from Memory, C0-&gt;A = 0<br> — — State — -<br>M = A = 0<br>C0 = A = 0<br><br>Step 2<br>P1 loads A from Memory, C1-&gt;A = 0<br> — — State — -<br>C0 = A = 0<br>C1 = A = 0<br>M = A = 0<br><br>Step 3<br>P0 stores 1 to A in its cache, C0-&gt;A = 1<br> — — State — -<br>C0 = A = 1<br>C1 = A = 0<br>M = A = 0<br><br>The cache becomes incoherent.</pre><p>To prevent this data difference we can use a group of techniques called <strong><em>cache coherence</em></strong>. There are many different types of cache coherent systems based on the amount processors in the system or technical decisions of the company that produces processors but we don’t need to know all of them. Instead, we can focus on a MESI protocol — it’s one of the most popular protocols. This protocol and a bunch of others can be grouped by the same technique they use to maintain coherence — invalidation. The basic idea is pretty simple: whenever the processor wants to modify a value in its cache it sends a signal through the connection line (bus, it connects processors, caches, and memory with each other) to the others to invalidate their caches.</p><p>MESI is an acronym for the 4 states of this protocol <strong><em>M</em></strong> — modified, <strong><em>E</em></strong> — exclusive, <strong><em>S</em></strong> — shared, and <strong><em>I</em></strong> — invalid. As we discussed before simply the processor architecture consists of 3 key CPU itself, CPU cache, and memory further we will introduce some additional components. All these states are needed to organize data sharing between multiple processors. Let’s take a look at each state:</p><p><strong><em>Modified</em></strong> — The CPU wrote a memory block to its cache and it guaranteed that the memory block belonged only to this CPU cache. The CPU owner of the memory block can read and write it without communicating with others CPUs. Example: CPU has a memory block in the state <strong><em>exclusive</em></strong> and it wants to modify it. It doesn’t need to send extra messages so it just changes the state to <strong><em>modified</em></strong>.</p><p><strong><em>Exclusive</em></strong> — is similar to <strong><em>modified</em></strong> except it was not changed by CPU. Example: CPU0 reads a memory block from Memory and puts it in its cache and there are no other owners (CPUs) of that memory block in the system.</p><p><strong><em>Shared</em></strong> — there are at least two caches that own the memory block. CPU can read it without communicating with other CPUs but cannot write the memory block without notifying others. Example: CPU has a memory block in the state <strong><em>shared</em></strong> and then it wants to modify it, CPU sends invalidate message to the other CPU and receives acknowledgment of invalidation, and then changes state to <strong><em>modified</em></strong>.</p><p><strong><em>Invalid</em></strong> — the memory block in an invalid state is not present in the CPU cache. On attempt to read the invalid memory block CPU will catch cache miss and will need to get it from Memory or other CPU caches. Example: CPU sends a read request to Memory and after it receives a response it writes a memory block on the spot that was invalidated before.</p><p>There are three types of messages that CPUs use to communicate with each other to access memory blocks in MESI architecture:</p><p>- Read request and read response — request for the specific memory block if CPU doesn’t own it. As result, it receives a response with the memory block. Read response can be received from another’s CPU cache or from the memory.</p><p>- Invalidate request and invalidate response — in the case when one processor wants to own a memory block (or basically to modify a <strong><em>shared</em></strong> memory block) it sends invalidate request to the other caches. Whenever the processor receives invalidate message it should set the memory block <strong><em>invalid</em></strong> state and then send acknowledge message to the sender.</p><p>Intentionally I didn’t specify <strong><em>read invalidate </em></strong>and <strong><em>writeback</em></strong> messages to keep the simplicity and since the model is abstract don’t overload the readers. Full table of messages and state diagram you can find in [5].</p><p>Let’s look at the first example to see how it works in MESI design:</p><pre>Initial data:<br>There are P0 and P1, addresses of A in both cache have <strong><em>invalid</em></strong> state. <br>Memory contains 0 by address A.<br><br>Step 1:<br>P0 sends read message for memory block A and receives read response from Memory.<br>C0-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 2:<br>P1 sends read message for memory block A and receives response from C0 cache <br>since it is faster than read from Memory.<br>C1-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 3:<br>P0 wants to modify C0-&gt;A. It sends invalidate message to the bus.<br><br>Step 4:<br>P1 receives invalidate message and turn its cache C1-&gt;A to <strong><em>invalid</em></strong> state. <br>Then it sends <strong><em>invalidate acknowledge</em></strong> to the bus.<br><br>Step 5:<br>P0 receives <strong><em>acknowledge</em></strong> message and change C0-&gt;A to <strong><em>exclusive</em></strong>.<br><br>Step 6:<br>P0 changes data by C0-&gt;A to 1 and set C0-&gt;A state to <strong><em>modified</em></strong>.</pre><p>As you can see the initial example became more complicated in MESI architecture. As mentioned before it is strongly recommended to read [5] because it contains a lot of specific details and examples.</p><p><strong><em>Invalidate</em></strong> and <strong><em>invalidate acknowledge</em></strong> is a pretty heavy operation if we consider them as a pair. CPU sends the <strong><em>invalidate</em></strong> message to the bus and then waits until it receives <strong><em>acknowledge</em></strong> message. To bypass that bottleneck was introduced <strong><em>store buffer</em></strong> for each CPU and its cache. So CPU puts the write operation into the buffer and continues to execute other operations until it will own the memory block. Let’s see how our example will be modified using <strong><em>store buffer</em></strong>:</p><pre>Initial data:<br>There are P0 and P1, addresses of A in both cache have <strong><em>invalid</em></strong> state. <br>Memory contains 0 by address A.<br><br>Step 1:<br>P0 sends read message for memory block A and receives read response from Memory.<br>C0-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 2:<br>P1 sends read message for memory block A and receives response from C0 cache <br>since it is faster than read from Memory.<br>C1-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 3:<br>P0 wants to modify C0-&gt;A. It sends invalidate message to the bus.<br><br>Step 4:<br>P0 puts operation A = 1 to the <strong><em>store buffer</em></strong>. <br>P0 continues execution instead of waiting but there is A = 0 in its cache.<br><br>…<br><br>Step N:<br>P1 receives invalidate message and sets state for A to <strong><em>invalid</em></strong>. <br>Then it sends <strong><em>invalidate acknowledge</em></strong> to the bus.<br><br>Step N + 1:<br>P0 receives <strong><em>acknowledge</em></strong> message and executes write to C0 <br>from its store buffer. It sets C0-&gt;A to <strong><em>modified</em></strong>.</pre><p><strong><em>Store buffer</em></strong> allows asynchronous execution of write operations but it also produces some issues we will discuss them after all the optimizations. In the case of intensive activity of reading and writing data, it causes invalidations very often. That dramatically affects the performance of the system. The operation of invalidation data by address is heavy in itself. CPU can put an invalidation operation into the queue and guarantee that invalidation will happen before any new operations on that memory block. This queue is called <strong><em>invalidate queue</em></strong> and it requires that invalidation of the memory block will be executed before sending any MESI messages related to that memory block. To demonstrate how it works let’s continue with our example:</p><pre>Initial data:<br>There are P0 and P1, addresses of A in both cache have <strong><em>invalid</em></strong> state. <br>Memory contains 0 by address A.<br><br>Step 1:<br>P0 sends read message for memory block A and receives read response from Memory.<br>C0-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 2:<br>P1 sends read message for memory block A and receives response from C0 cache <br>since it is faster than read from Memory.<br>C1-&gt;A becomes <strong><em>shared</em></strong> from <strong><em>invalid</em></strong> state.<br><br>Step 3:<br>P0 wants to modify C0-&gt;A. It sends invalidate message to the bus.<br><br>Step 4:<br>P0 puts operation C0-&gt;A = 1 to the <strong><em>store buffer</em></strong>. <br>P0 continues execution instead of waiting.<br><br>…<br><br>Step N:<br>P1 receives invalidate message and puts invalidate operation <br>into the <strong><em>invalidate queue</em></strong> and sends <strong><em>acknowledge</em></strong> message to the bus.<br><br>Step N + 1:<br>P0 receives <strong><em>acknowledge</em></strong> message and executes write to C0 from its store buffer.<br>It sets C0-&gt;A to <strong><em>modified</em></strong>.<br><br>…<br><br>Step N + M:<br>P1 executes invalidate operation on C1-&gt;A memory block.</pre><p>There is one important rule important to mention: if the processor needs to access the memory block it can do it directly from its own <strong><em>store buffer</em></strong> (if it persists there) but it cannot access other processors&#39; <strong><em>store buffers</em></strong>. And it is the opposite with <strong><em>invalidate queue</em></strong> — processors cannot access it.</p><p>You’ve probably noticed that <strong><em>store buffer</em></strong> and <strong><em>invalidate queue</em></strong> bring more asynchrony in the execution. That’s actually how the reorders happen on the hardware level. While <strong><em>store buffer </em></strong>holds the write operation the cache is still stored to the old value. Pretty much the same with <strong><em>invalidate queue</em></strong> the cache stores the memory block even if it has invalidation of it in <strong><em>invalidate queue</em></strong>.</p><p>Here is the full scheme of the multi-processor architecture:</p><figure><img alt="Abstract scheme of multe-processor architecture" src="https://cdn-images-1.medium.com/max/1024/1*lsHinEqkHnHg-u34GDEvhQ.png" /></figure><p>To see how it can happen let’s check the example with Peterson’s lock:</p><pre>Initial data:<br>A = 0 is stored in cache C1 of P1 in state <strong><em>exclusive</em></strong>, <br>B = 0 is stored in cache of C0 in state <strong><em>exclusive</em></strong>, <br>turn = 0 is stored in C0 and C1 as <strong><em>shared</em></strong>.<br><br>P0:<br>// Step 1, Step 4, Step 5, Step 11<br>A = 1<br>// Step 12,<br>turn = 1<br><br>// Step 13<br>while (B == 1 &amp;&amp; turn == 1) { }<br>// Step 14, Step 15<br>// Do some work<br>A = 0<br><br>P1:<br>// Step 2, Step 3, Step 6<br>B = 1<br>// Step 7, Step 8<br>turn = 0<br><br>// Step 9<br>while (A == 1 &amp;&amp; turn == 0) { }<br>// Step 10<br>// Do some work<br><br>B = 0<br><br>Step 1:<br>P0 wants to change A but it doesn’t have A in C0. <br>So it sends read message to the bus.<br><br>Step 2:<br>P1 wants to change B but it doesn’t have B in C1. <br>So it sends read message to the bus.<br><br>Step 3:<br>P1 receives read message and change state of A to <strong><em>shared</em></strong> <br>and returns response message.<br><br>Step 4:<br>P0 receives read message and change state of B to <strong><em>shared</em></strong> <br>and returns respond message.<br><br>Step 5:<br>P0 receives response with A = 0 and put operation A = 1 in store buffer <br>and sends invalidation for A.<br><br>Step 6:<br>P1 receives response with B = 0 and put operation B = 1 in store buffer <br>and sends invalidation for B.<br><br>Step 7:<br>P1 already has turn = 0 in its cache so it doesn’t need to modify it<br><br>Step 8:<br>P1 receives invalidation for A put it in invalidate queue <br>and sends acknowledge but in C1 still A = 0<br><br>Step 9:<br>P1 has turn = 0 in C1 and A = 0 (even it received invalidation for A) and <br>B = 1 in store buffer. <br>It can read A from store buffer so (A == 1 &amp;&amp; turn == 0) returns false. <br>P1 enters critical section.<br><br>Step 10:<br>P1 enters critical section.<br><br>Step 11:<br>P0 receives acknowledge for A so it executes A = 1 to its cache C0 <br>from the store buffer and changes state to <strong><em>modified</em></strong>.<br><br>Step 12:<br>P0 wants to modify turn so it puts turn = 1 in store buffer <br>and sends invalidate.<br><br>Step 13:<br>P0 has A = 1, B = 0 and it has turn = 1 in its store buffer <br>so condition (B == 1 &amp;&amp; turn == 1) returns false.<br><br>Step 14:<br>P0 enters critical section but P1 has already entered it.<br><br>Step 15:<br>P0 receives invalidation for B but it’s too late <br>since P0 and P1 in the critical section at the same time.</pre><p>There is chaos here with the reorderings. That’s why we need to use memory barriers to bring some order to the execution. Let’s introduce some abstract barriers: <strong>Load Load</strong> barrier operates with <strong><em>invalidate queue</em></strong> so the call of that barrier forces to execute all the invalidate messages in the queue which means it makes an order between the load operations before the barrier and after it (that’s why <strong>Load Load</strong>). <strong>Store Store</strong> barrier operates with <strong><em>store buffer</em></strong> and it flushes all store operations from the buffer which implies the store operations before the barrier will be ordered with the store operations after. Now we can use barriers to fix Peterson’s algorithm.</p><pre>Initial data:<br>A = 0 is stored in cache C1 of P1 in state exclusive, <br>B = 0 is stored in the cache of C0 of P0 in state exclusive, <br>turn = 0 is stored in C0 and C1 as shared.<br><br>P0:<br>// Step 1, Step 4, Step 5, Step 10<br>A = 1<br>// Step 11<br>turn = 1<br><br>// Step 12, Step 13, Step 17<br><strong>Store Store barrier</strong><br><br>// Step 18, Step 19<br><strong>Load Load barrier</strong><br><br>// Step 20, Step 21, Step 23<br>while (B == 1 &amp;&amp; turn == 1) { }<br><br>// Step 24, Step 28<br>// Do some work<br><br>// Step N<br>A = 0<br>// Step N + 3, Step N + 6<br><br>P1:<br>// Step 2, Step 3, Step 6<br>B = 1<br>// Step 7, Step 8<br>turn = 0<br><br>// Step 9, Step 14, Step 15<br><strong>Store Store barrier</strong><br><br>// Step 16, Step 22, Step 25<br><strong>Load Load barrier</strong><br><br>// Step 26, Step 27, Step 29, Step 30, Step N + 1, Step N + 2, <br>// Step N + 4, Step N + 5, Step N + 7<br>while (A == 1 &amp;&amp; turn == 0) { }<br><br>// Step N + 8<br>// Do some work<br><br>B = 0<br><br>Step 1:<br>P0 wants to change A but it doesn’t have A in the C0. <br>So it sends a read message to the bus.<br><br>Step 2:<br>P1 wants to change B but it doesn’t have B in the C1. <br>So it sends a read message to the bus.<br><br>Step 3:<br>P1 receives the read message and changes the state of A to <strong><em>shared</em></strong> <br>and returns a response message.<br><br>Step 4:<br>P0 receives the read message and changes the state of B to <strong><em>shared</em></strong> <br>and returns the respond message.<br><br>Step 5:<br>P0 receives a response with A = 0 and put operation A = 1 in store buffer <br>and sends invalidation for A.<br><br>Step 6:<br>P1 receives a response with B = 0 and put operation B = 1 in store buffer <br>and sends invalidation for B.<br><br>Step 7:<br>P1 already has turn = 0 in its cache so it doesn’t need to modify it.<br><br>Step 8:<br>P1 receives invalidation for A put it in invalidate queue <br>and sends acknowledge but in C1 still A = 0<br><br>Step 9:<br>P1 executes <strong>Store Store barrier</strong> so now it needs to execute all the operations <br>in store buffer. It waits until it gets acknowledge for B.<br><br>Step 10:<br>P0 receives acknowledge for A so it executes A = 1 to its cache C0 <br>from the store buffer and changes state to <strong><em>modified</em></strong>.<br><br>Step 11:<br>P0 wants to modify <em>turn</em> so it puts turn = 1 in store buffer <br>and sends invalidate.<br><br>Step 12:<br>P0 executes <strong>Store Store barrier</strong> so now it needs to execute all the operations <br>in store buffer. It waits until it gets acknowledge for <em>turn</em>.<br><br>Step 13:<br>P0 receives invalidation for B then P0 puts invalidation of B <br>in invalidate queue and sends acknowledge.<br><br>Step 14:<br>P1 receives acknowledge for B and then executes B = 1 in C1 <br>and set state <strong><em>modified</em></strong> for B.<br><br>Step 15:<br>P1 receives invalidation for <em>turn</em> so it puts it in invalidate queue <br>and sends acknowledge.<br><br>Step 16:<br>P1 executes <strong>Load Load barrier</strong>. It needs to empty invalidate queue. <br>There are invalidations of A and <em>turn</em>.<br><br>Step 17:<br>P0 receives acknowledge for <em>turn</em> and executes turn = 1 <br>from its store buffer and changes state to <strong><em>modified</em></strong>.<br><br>Step 18:<br>P0 executes <strong>Load Load barrier</strong>. It needs to empty invalidate queue. <br>There is an invalidation of B.<br><br>Step 19:<br>P0 invalidates B from invalidate queue and sets the state to <strong><em>invalid</em></strong> for B. <br>Now it can continue execution.<br><br>Step 20:<br>P0 has A = 1 and turn = 0 in its cache. <br>To execute condition (B == 1 &amp;&amp; turn == 1) it needs to read B.<br><br>Step 21:<br>P0 sends a read request for B.<br><br>Step 22:<br>P1 receives a read request for B and returns a response with B = 1 <br>and changes the state of B to <strong><em>shared</em></strong>.<br><br>Step 23:<br>P0 receives read response with B = 1 and set state <strong><em>shared</em></strong> for B.<br><br>Step 24:<br>P0 has A = 1, B = 1, turn = 0 so condition (B == 1 &amp;&amp; turn == 1) <br>and return false. P0 enters the critical section.<br><br>Step 25:<br>P1 invalidates A and <em>turn</em> from invalidate queue and <br>set their states to <strong><em>invalid</em></strong>. Now it can continue execution.<br><br>Step 26:<br>P1 has B = 1 in C1. <br>To execute condition (A == 1 &amp;&amp; turn == 0) it needs to read A and <em>turn</em>.<br><br>Step 27:<br>P1 sends two read requests for A and for <em>turn</em>.<br><br>Step 28:<br>P0 receives two requests for A and for <em>turn</em> and returns two responses <br>for A: A = 1 and <br>for <em>turn</em>: turn = 0 <br>and change their states to <strong><em>shared</em></strong>.<br><br>Step 29:<br>P1 receives two read responses with A = 1 and turn = 0 <br>and sets them state <strong><em>shared</em></strong>.<br><br>Step 30:<br>P1 has A = 1, B = 1, turn = 0 so condition (A == 1 &amp;&amp; turn == 0) <br>and returns true. <br>P1 cannot enter the critical section and continues executing <strong><em>while</em></strong>.<br><br>…<br><br>P0 does work in the critical section<br>P1 executes while loop<br><br>…<br><br>Step N:<br>P0 finishes with its work in the critical section and wants to change A. <br>A is in <strong><em>shared</em></strong> state, so it puts A = 0 in store buffer <br>and sends invalidate message.<br><br>Step N + 1:<br>P1 receives invalidate message for A and puts the invalidation of A <br>in invalidate queue then P1 sends acknowledge.<br><br>Step N + 2:<br>P1 continues executing while loop with the old value of A.<br><br>Step N + 3:<br>P0 receives acknowledge and executes A = 0 from store buffer <br>then sets the state of A to <strong><em>modified</em></strong>.<br><br>Step N + 4:<br>P1 executes invalidation of A from invalidate queue <br>and set state of A to <strong><em>invalid</em></strong>. So it doesn’t have A in its cache C1.<br><br>Step N + 5:<br>P1 sends a read message for A.<br><br>Step N + 6:<br>P0 receives the read message for A and changes the state of A to <strong><em>shared</em></strong> <br>then returns a response with A = 0.<br><br>Step N + 7:<br>P1 receives a response with A = 0 it and stores it in its cache C1 <br>then sets <strong><em>shared</em></strong> state for A.<br><br>Step N + 8:<br>P1 has A = 0, B = 1, turn = 0 in C1. <br>So condition (A == 1 &amp;&amp; turn == 0) returns false. <br>P1 enters the critical section.</pre><p>As you can see it takes a lot of steps to organize the cross-processor execution of Peterson’s algorithm and you can guess that barrier is not so cheap operation for the CPUs. We employed two barriers <strong>Store Store</strong> and <strong>Load Load</strong>. The first one operates with <strong><em>store buffer</em></strong> and the second one operates with <strong><em>invalidate queue</em></strong>. The names abstract barriers <strong>Store Store</strong> barrier and <strong>Load Load</strong> barrier commands have been chosen because the real commands are different for the processor architecture and for the platform. There are also other types of barriers existing that we didn’t use in the Peterson lock example.</p><p>Let’s see all types of barriers we can encounter:</p><p><strong>Load Load</strong> — a familiar barrier we used in the article it operates only with <strong><em>invalidate queue</em></strong> (empties it) and guarantees an order for load instructions so the operations before the barrier will not interfere with the scope after it.</p><p><strong>Store Store</strong> — the barrier we used above, works with the <strong><em>store buffer</em></strong> and provides an order for store (write) instructions so the operations before the barrier will not mix up with the operations after it.</p><p><strong>Load Store</strong> — barrier that guarantees that load operations before the barrier will not be executed after it and store operations after the barrier will not be executed before it, considered to be a light barrier.</p><p><strong>Store Load </strong>— a rare barrier that makes an order between preceding store operations and following load operations. It is considered to be the heaviest of all the barriers.</p><h4>Architecture specifics</h4><p>In the different CPU architectures, the memory orderings are organized differently. If in x86 processors the ordering is pretty strong and in ARM architectures it’s actually opposite the memory model there is more relaxed. It’s important to know due to Apple&#39;s move to ARM machines.</p><pre>                                          ARMv7     x86<br>Loads can be reordered after loads        Yes       No<br>Loads can be reordered after stores       Yes       No<br>Stores can be reordered after stores      Yes       No<br>Stores can be reordered after loads       Yes       Yes<br>Atomic can be reordered with loads        Yes       No<br>Atomic can be reordered with stores       Yes       No<br>Dependent loads can be reordered          No        No<br>Incoherent instruction cache/pipeline     Yes       Yes</pre><p>So you can see that ARM can reorder instructions almost in any direction compared to x86. That makes ARM more performant because of optimizations processor can make but it’s more complicated to manage the correctness of code in such a relaxed memory model. Important to add that in ARMv8 the memory model was simplified [4]. But we don’t need to specify all the barriers manually instead we can use the memory model that Swift brought from C++. So the compiler will set memory barriers for us. We will discuss the memory model in the next part.</p><h4>Performance</h4><p>There are a lot of discussions about the usage of lock-free algorithms. First of all, it’s very difficult to implement them correctly and debug to find a mistake as you probably understand looking at the number of problems with the instructions reordering and etc. Another issue is that atomic operations can be heavy and slower than nonatomic operations. So sometimes there is no gain in the usage of lock-free algorithms especially if they are using atomic operations all over the algorithm instead of the critical section.</p><h4>Conclusion</h4><p>Today we didn’t write a single line in Swift but the goal of this article was an explanation of how a system with multiple CPUs shares memory. In the next article, we will start with the memory model for Swift and will introduce atomic RMW operations because they are fundamental parts of the Lock-free algorithms. Also, we will discuss what is ABA problem and how we potentially solve it.</p><h4>Sources</h4><ol><li>Maurice Herlihy, Nir Shavit, Victor Luchangco, Michael Spear. The Art of Multiprocessor Programming.</li><li><a href="https://youtu.be/5sZo3SrLrGA">MIT 6.172 Performance Engineering of Software Systems. Charles Leiserson. Synchronization Without Locks</a>.</li><li><a href="https://www.youtube.com/playlist?list=PLeWkeA7esB-OgNoVkE2lW2cVBxpDbu92h">Ben H. Juurlink. Multicore Architectures course</a>.</li><li><a href="https://www.cl.cam.ac.uk/~pes20/armv8-mca/armv8-mca-draft.pdf">ARMv8 Memory model</a>.</li><li><a href="http://www.rdrop.com/~paulmck/scalability/paper/whymb.2010.06.07c.pdf">Memory Barriers: a Hardware View for Software Hackers</a>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sHOcSgP1nnVsB8gmr3LPBg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1f8b2e6ea6f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Primitives in Depth: Semaphore and Group]]></title>
            <link>https://shchukin-alex.medium.com/gcd-primitives-in-depth-part-1-4d1b06117be6?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d1b06117be6</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[gcd]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Wed, 19 Oct 2022 21:57:05 GMT</pubDate>
            <atom:updated>2023-05-25T13:26:39.379Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article, we will implement some of the GCD classes using low-level primitives to understand how GCD is actually functioning.</p><h4><strong>Semaphore</strong></h4><p>We’re going to begin with DispatchSemaphore, although what we’ll be implementing is a simplified version. The real DispatchSemaphore has a much more specific set of functionalities. To refresh your knowledge about DispatchSempahore you can check the <a href="https://shchukin-alex.medium.com/gcd-synchronization-c74f13a800ca">article about synchronization</a>.</p><p>Now we can start to implement the Semaphore. There is an initialization section in the code snippet below:</p><pre>final class Semaphore {<br>  private var mutex = pthread_mutex_t()<br>  private var condition = pthread_cond_t()<br>  private var counter = 0<br>  private var maxCount = 0<br>  private let queue: Queue = Queue()<br><br>  // maxCount - max thread amount waiting for signal<br>  init(maxCount: Int = 0) {<br>     pthread_mutex_init(&amp;mutex, nil)<br>     pthread_cond_init(&amp;condition, nil)<br>     self.maxCount = maxCount<br>  }<br>  deinit {<br>     pthread_mutex_destroy(&amp;mutex)<br>     pthread_cond_destroy(&amp;condition)<br>  }<br>}</pre><p>In this section, we use pthread_mutex_t and pthread_cond_t, types from the POSIX thread library, written in C. pthread_mutex_t serves as a lock, allowing us to ensure thread-safe access to resources, such as counter. pthread_cond_t can block the calling thread via pthread_cond_wait and resume it using pthread_cond_signal.</p><p>To initialize these types, we employ pthread_mutex_init and pthread_cond_init respectively. It’s important to note of deinitialization: these primitives aren’t self-destructing. You must manually terminate them using pthread_mutex_destroy or pthread_cond_destroy.</p><p>Important to know that, pthread_cond_signal can unblock at least one thread if there are several threads blocked by pthread_cond_wait and<strong><em> </em></strong>the <em>scheduling policy</em> shall determine the order of the unblocking. In macOS by default, the order is determined by the priority which doesn’t correspond with the semaphore logic. To bypass this limitation we will employ pthread_cond_broadcast which unblocks all pthread_cond_wait used on the condition. And to provide sequential order we introduce FIFO Queue which we will use later on in the wait method. There is an implementation of Queue in the code snippet below:</p><pre>    fileprivate final class Queue {<br>        private var elements: [Int] = []<br><br>        func enqueue(element: Int) {<br>            elements.append(element)<br>        }<br><br>        func dequeue() -&gt; Int? {<br>            guard !elements.isEmpty else { return nil }<br><br>            return elements.removeFirst()<br>        }<br>        <br>        func peek() -&gt; Int? {<br>            guard !elements.isEmpty else { return nil }<br><br>            return elements.first<br>        }<br><br>        var isEmpty: Bool {<br>            return elements.isEmpty<br>        }<br>    }</pre><p>Let’s start with the simple implementation of the method wait. It blocks the calling thread using condition until it gets the signal.</p><pre>func wait() {<br>   pthread_mutex_lock(&amp;self.mutex)<br>   pthread_cond_wait(&amp;self.condition, &amp;self.mutex)<br>   pthread_mutex_unlock(&amp;self.mutex)<br>}</pre><p>That will work with one thread solution but we need to make it works with multiple threads. Let’s improve wait using maxCount variable. Whenever counter is bigger than maxCount it calls pthread_cond_wait. So counter regulates the throughput of Semaphore. It means that the larger counter the more threads can execute wait simultaneously.</p><pre>func wait() {<br>  pthread_mutex_lock(&amp;self.mutex)<br>  counter += 1<br>  while(self.counter &gt; self.maxCount) {<br>    pthread_cond_wait(&amp;self.condition, &amp;self.mutex)<br>  }<br>  pthread_mutex_unlock(&amp;self.mutex)<br>}</pre><p>Recall that pthread_cond_signal does not ensure the order in which threads are awakened. To resolve this, we will use pthread_cond_broadcast. This function wakes all threads that have been blocked by pthread_cond_wait. However, we desire to put only the earliest thread in the awake state and return the others to a waiting state. To accomplish this, we must keep track of the order in which the wait calls are made.</p><p>Here, we introduce a local variable tmpCounter to keep track of the order. Being local, tmpCounter will have a unique value for each wait call. Then we enqueue this counter into a Queue. If the value of the counter doesn’t match tmpCounter we put the thread on wait again. This ensures that we manage the concurrency of our threads in the FIFO order.</p><pre>func wait() {<br>  pthread_mutex_lock(&amp;self.mutex)<br><br>  counter += 1<br>  let tmpCounter = counter<br>  while(self.counter &gt; self.maxCount &amp;&amp; self.queue.peek() != tmpCounter) {<br>    queue.enqueue(element: tmpCounter)<br>    pthread_cond_wait(&amp;self.condition, &amp;self.mutex)<br>  }<br><br>  pthread_mutex_unlock(&amp;self.mutex)<br>}</pre><p>Now we need to implement the signal function. It decreases the counter we use to keep the semaphore waiting in the while loop and broadcast to all waits.</p><pre>func signal() {<br>  pthread_mutex_lock(&amp;mutex)<br>  <br>  counter -= 1<br>  pthread_cond_broadcast(&amp;condition)<br>  pthread_mutex_unlock(&amp;mutex)<br>}</pre><p>Let’s test the semaphore to check how it works. In the example below the calling thread gets blocked by the method wait for 5 seconds until the global queue calls the signal method:</p><pre>let semaphore = Semaphore(maxCount: 0)<br>print(“Start”)<br>DispatchQueue.global().async {<br>  sleep(5)<br>  semaphore.signal()<br>}<br>semaphore.wait()<br>print(“Finish”)</pre><p>Result:</p><pre>Start<br>← 5 second →<br>Finish</pre><p>Another example shows how semaphore works with multiple threads. Whenever we call the method wait it increases counter by 1. Then it prints <em>test1</em> or <em>test2</em> (depending on which queue gets acquired faster) and then sleeps for 5 seconds. Then it’s time for the second call of the method wait in another queue. It increases the counter to 2 and since maxCount is 1 it puts the queue on hold because condition<strong> </strong>self.counter &gt; self.maxCount returns a negative value.</p><pre>let semaphore = Semaphore(maxCount: 1)<br>DispatchQueue.global().async {<br>  semaphore.wait()<br>  print(“test1”)<br>  sleep(5)<br>  semaphore.signal()<br>}<br><br>DispatchQueue.global().async {<br>  semaphore.wait()<br>  print(“test2”)<br>  sleep(5)<br>  semaphore.signal()<br>}</pre><p>Result:</p><pre>test1<br>← 5 second →<br>test2</pre><h4><strong>Group</strong></h4><p>In this part, we will implement the class Group with similar functionality to DispatchGroup. To refresh your knowledge you can check <a href="https://shchukin-alex.medium.com/gcd-dispatchgroup-and-concurrentperform-f0e52706748">the article about groups</a>. Let’s define private variables. There are pthread_mutex_t and pthread_cond_t known from the Semaphore section and counter to count how many times enter was called.</p><pre>final class Group {<br>  private var mutex = pthread_mutex_t()<br>  private var condition = pthread_cond_t()<br>  private var counter = 0<br>  <br>  init() {<br>    pthread_mutex_init(&amp;mutex, nil)<br>    pthread_cond_init(&amp;condition, nil)<br>  }<br>  <br>  deinit {<br>    pthread_mutex_destroy(&amp;mutex)<br>    pthread_cond_destroy(&amp;condition)<br>  }<br>}</pre><p>DispatchGroup has two primary methods enter and leave to enter and leave the group accordingly. enter thread-safely increases the counter and leave thread-safely decreases the counter and calls the pthread_cond_broadcast to unlock the threads blocked by the pthread_cond_wait<strong><em> </em></strong>methods. Using group we don’t have any order of wait calls instead we need to unblock all the waits through pthread_cond_broadcast .</p><p>In the code below there are implementations of these functions:</p><pre>func enter() {<br>  pthread_mutex_lock(&amp;mutex)<br>  counter += 1<br>  pthread_mutex_unlock(&amp;mutex)<br>}<br><br>func leave() {<br>  pthread_mutex_lock(&amp;mutex)<br>  <br>  counter -= 1<br>  pthread_cond_broadcast(&amp;self.condition)<br>  pthread_mutex_unlock(&amp;mutex)<br>}</pre><p>Method wait blocks the calling thread until pthread_cond_wait receives a signal from the method leave and counter is 0:</p><pre>func wait() {<br>  pthread_mutex_lock(&amp;self.mutex)<br>  while(self.counter != 0) {<br>    pthread_cond_wait(&amp;self.condition, &amp;self.mutex)<br>  }<br>  pthread_mutex_unlock(&amp;self.mutex)<br>}</pre><p>pthread_cond_wait takes two parameters condition and mutex. It’s pretty clear why it requires a condition but the mutex parameter needs more consideration. pthread_cond_wait atomically unlocks the mutex during its execution and locks the mutex after it finishes. That is not obvious why the condition function operates with a mutex. If you notice, the methods working with the condition are guarded with locks. That’s needed to prevent modification of the variable that regulates the condition (in our case counter). We wouldn’t be able to call pthread_cond_signal (since it’s covered with the mutex) if the pthread_cond_wait would not release the mutex on its execution. For a better understanding here is the scheme:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9VoJgHwhZAptGML-5PGawA.png" /><figcaption>Group call stack and lock state</figcaption></figure><p>Now we can implement the example from <a href="https://shchukin-alex.medium.com/gcd-dispatchgroup-and-concurrentperform-f0e52706748">the article</a> and use Group class. There are 2 tasks executing in the concurrent queue in the same Group and there is a method wait that blocks the calling thread until both tasks are done. As you can see the result is the same if we would use DispatchGroup.</p><pre>let group = Group()<br>let concurrentQueue = DispatchQueue(label: “com.test.testGroup”, attributes: .concurrent)<br><br>group.enter()<br>concurrentQueue.async {<br>  sleep(1)<br>  print(“test1”)<br>  group.leave()<br>}<br><br>group.enter()<br>concurrentQueue.async {<br>  print(“test2”)<br>  group.leave()<br>}<br>group.wait()<br><br>print(“All tasks were executed”)</pre><p>Result:</p><pre>test2<br>← 1 second →<br>test1<br>All tasks were executed</pre><p>Today we discussed how to implement some GCD classes: Semaphore and Group. Of course, these methods are simplistic versions of the real DispatchSempahore and DispatchGroup but it is a good exercise to try to create them ourselves for educational goals.</p><p><strong>Update</strong>: The was a mistake in the article which I found after publishing. It was about incorrect usage pthread_cond_signal (which doesn’t guarantee the wake order) instead of pthread_cond_broadcast . Now, everything is fixed with the explanations.</p><p>All the implementations you can find by following this <a href="https://github.com/shchukin-alex/MultighreadingPrimitives">link</a>.</p><p>Check my <a href="https://twitter.com/ShchukinAleks">Twitter</a> to get the newest updates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gkYjSTrYy7FfO7G7vnMPQg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d1b06117be6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Part 5: DispatchSource and Target Queue Hierarchy]]></title>
            <link>https://shchukin-alex.medium.com/gcd-dispatchsource-and-target-queue-hierarchy-4c554ea10fd2?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c554ea10fd2</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[grand-central-dispatch]]></category>
            <category><![CDATA[concurrency]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Mon, 18 Apr 2022 13:08:49 GMT</pubDate>
            <atom:updated>2022-10-21T21:09:59.601Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article, we will discuss some niche concepts such as DispatchSource and target queue hierarchy.</p><h4><strong>DispatchSource</strong></h4><p><strong><em>DispatchSource</em></strong> is the fundamental type that handles system events. It can be an event listener for different types like file system events, signals, memory warnings and etc.</p><p>I don’t think we often use this construction in daily work but in some cases, it’s important to be aware of it especially if you work with low-level functionality. Further, we will look through some subtypes you can find useful in your apps.</p><p>We will start with the most known Dispatch source - <strong><em>DispatchSourceTimer</em></strong>. As you can guess by its name it works like a simple timer and generates periodical notifications which you can process in the event handler. Here is a simple example in the code snippet below:</p><pre>let timerSource = DispatchSource.makeTimerSource()</pre><pre>func testTimerDispatchSource() {<br>  timerSource.setEventHandler {<br>    print(“test”)<br>  }<br>  timerSource.schedule(deadline: .now(), repeating: 5)<br>  timerSource.resume()<br>}</pre><p>It prints the word “test” in the console every 5 seconds.</p><p><strong>Important</strong>: <em>You should keep the reference to the data source somewhere in your code otherwise it will be deallocated and you will not be able to catch events.</em></p><p><strong><em>DispatchSourceMemory</em></strong> will help to handle memory issues you can encounter during the application work time. It can be useful if you want to have a central place in your architecture that logs memory issues. In the example below, it shows how you can listen for the memory warnings. You can simulate memory warning in the simulator using Debug -&gt; Simulate Memory Warning.</p><pre>let memorySource = DispatchSource.makeMemoryPressureSource(eventMask: .warning, queue: .main)</pre><pre>func testMemoryDispatchSource() {<br>  memorySource.setEventHandler {<br>    print(“test”)<br>  }<br>  memorySource.resume()<br>}</pre><p><strong><em>DispatchSourceSignal</em></strong> will track all the UNIX signals sent to the application. That can be useful if you are developing a console application. In the example below we are catching the SIGSTOP signal. To emulate that one you can press the Pause button and then Resume in the debugger panel in Xcode.</p><pre>let signalSource = DispatchSource.makeSignalSource(signal: SIGSTOP, queue: .main)</pre><pre>func testSignalSource() {<br>  signalSource.setEventHandler {<br>    print(“test”)<br>  }<br>  signalSource.resume()<br>}</pre><p>Using <strong><em>DispatchSourceProcess</em></strong> we can listen to other processes for receiving signals or making forks. For example, you can use it to monitor other processes in the non-iOS application. All the events you can find in <strong><em>DispatchSource.ProcessEvent</em></strong>. In the example below we will listen to our process of receiving signals similar to what we did in the previous example. <strong><em>ProcessInfo.processInfo.processIdentifier </em></strong>returns processId of the current process.</p><pre>let processSource = DispatchSource.makeProcessSource(identifier: ProcessInfo.processInfo.processIdentifier, eventMask: .signal, queue: .main)</pre><pre>func testProcessSource() {<br>  processSource.setEventHandler {<br>    print(“test”)<br>  }<br>  processSource.resume()<br>}</pre><p>As you can see the syntax of the event handling looks identically in all the examples and I hope it can provide you with some gasp on how and when you can use <strong>DispatchSource</strong> inside your applications.</p><p><strong>Important</strong>: <em>Do not forget to call the method </em><strong><em>cancel</em></strong><em> or </em><strong><em>suspend</em></strong><em> after you finish using </em><strong><em>DispatchSource</em></strong><em>.</em></p><p>It was a quick review of different DispatchSource subtypes and how to work with them. To find out how to work with <strong><em>DispatchSourceFileSystemObject</em></strong> I can recommend you to go through <a href="https://swiftrocks.com/dispatchsource-detecting-changes-in-files-and-folders-in-swift">this article</a>.</p><h4><strong>Target queue hierarchy</strong></h4><p>That is an important concept to understand. Let’s say we have multiple queues in the app. We can redirect the execution of their tasks to one specific queue which is called the <strong><em>target queue</em></strong>. In the example below you can see 4 serial queues: 1 target queue and 3 others where the target queue is specified. To check that all the tasks are executing on the same target queue we will print current thread information. As you can see the thread is the same in all the cases. The target queue has <em>utility</em> QoS and it means that all the tasks executing on it will not have QoS less than the <em>utility</em>. Indeed, we can see the queue which has <em>background</em> QoS is executing on <em>utility</em> instead. The queue which doesn’t have a QoS will be executed on <em>userInitiated </em>because we are creating it from the main queue so it acquires <em>userInteractive</em> and decreases to <em>userInitiated</em> according to QoS rules. Learn more about the QoS you can <a href="https://medium.com/@shchukin-alex/gcd-dispatchworkitem-and-quality-of-service-f206efdbf36f">here</a>.</p><pre>let targetQueue = DispatchQueue(label: “com.test.targetQueue”, qos: .utility)</pre><pre>let queue1 = DispatchQueue(label: “com.test.queue1”, target: targetQueue)</pre><pre>let queue2 = DispatchQueue(label: “com.test.queue2”, qos: .background, target: targetQueue)</pre><pre>let queue3 = DispatchQueue(label: “com.test.queue3”, qos: .userInteractive, target: targetQueue)</pre><pre>targetQueue.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>  print(Thread.current)<br>}</pre><pre>queue1.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)</pre><pre>  print(Thread.current)<br>}</pre><pre>queue2.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)</pre><pre>  print(Thread.current)<br>}</pre><pre>queue3.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>  <br>  print(Thread.current)<br>}</pre><p>Result:</p><pre>utility<br>&lt;NSThread: 0x600003ec6600&gt;{number = 6, name = (null)}</pre><pre>userInitiated<br>&lt;NSThread: 0x600003ec6600&gt;{number = 6, name = (null)}</pre><pre>utility<br>&lt;NSThread: 0x600003ec6600&gt;{number = 6, name = (null)}</pre><pre>userInteractive<br>&lt;NSThread: 0x600003ec6600&gt;{number = 6, name = (null)}</pre><p>All the tasks which will be enqueued to queue1, queue2, and queue3 will be executed in the target queue. If we would not use the target queue we can encounter a situation when <a href="https://shchukin-alex.medium.com/gcd-queues-and-methods-f12453f529e7"><strong><em>thread explosion</em></strong></a> could occur because each serial queue executes the task on its own thread and that can produce massive context switching. So the target queue is preventing this scenario.</p><p>Based on that idea Apple recommends we use one target queue per subsystem which ofc very reasonable: having a small number of serial queues (and respectively threads) is more efficient than having a lot working in parallel.</p><p>Today we learned different types of <strong><em>DispatchSource</em></strong> and how to work with the <strong><em>target queue hierarchy</em></strong>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c554ea10fd2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Part 4: Synchronization]]></title>
            <link>https://shchukin-alex.medium.com/gcd-synchronization-c74f13a800ca?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c74f13a800ca</guid>
            <category><![CDATA[synchronization]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[multithreading]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Fri, 19 Nov 2021 12:47:29 GMT</pubDate>
            <atom:updated>2022-10-20T19:14:09.520Z</atom:updated>
            <content:encoded><![CDATA[<p>The topic of today’s article is synchronization. It’s one of the most important concepts in multithreading. And we will see how we can provide thread safety using GCD primitives.</p><h4>Semaphore</h4><p>And the first primitive we will consider is <strong><em>DispatchSemaphore</em></strong>. I guess you’ve heard about mutex before. Briefly, it’s a construction that helps us to limit access to the resource in the concurrent environment. Mutex provides access to the resource for only one thread at the same time. In contrast, semaphore can be set up to provide multiple access to the threads. You can variate the number of threads which can get access to the resource in the constructor of <strong><em>DispatchSemaphore</em></strong>. Basically, <strong><em>DispatchSemaphore</em></strong> is a counter with two methods signal and wait. Method signal increments the counter and method wait decrements it. As you can see <strong><em>DispatchSemaphore</em></strong> has constructor with the parameter value which initiates the internal counter. We will try to implement semaphore and other GCD primitives ourselves in <a href="https://shchukin-alex.medium.com/gcd-primitives-in-depth-part-1-4d1b06117be6">one of the following articles</a> to make it more clear.</p><p>In the example below, we will initiate the semaphore with 0. Then asynchronously add our task to the global queue and block the calling thread by the wait method. When the task will be completed it will call the signal method which in another hand will unblock the calling thread blocked by wait. <strong>Important</strong>: never block the main thread with the method wait since all the UI tasks are executing on it.</p><pre>let semaphore = DispatchSemaphore(value: 0)</pre><pre>DispatchQueue.global().async {<br>  print(“test1”)<br>  sleep(1)<br>  semaphore.signal()<br>}</pre><pre>semaphore.wait()<br>print(“test2”)</pre><p>Result:</p><pre>test1<br>← 3 seconds →<br>test2</pre><p>Now let’s implement thread-safe property using the semaphore. Actually, there are more easy and efficient ways to do that but again this example will be good for the educational goals. I guess it looks familiar to those who know how to work with the locks.</p><pre>let semaphore = DispatchSemaphore(value: 1)</pre><pre>private var internalResource: Int = 0<br>var resource: Int {<br>  get {<br>    defer {<br>      semaphore.signal()<br>    }<br>    semaphore.wait()<br>    return internalResource<br>  }<br>  set {<br>    semaphore.wait()</pre><pre>    print(newValue)<br>    internalResource = newValue<br>    sleep(1)</pre><pre>    semaphore.signal()<br>  }<br>}</pre><pre>let group = DispatchGroup()<br>DispatchQueue.global().async(group: group) {<br>  resource = 1<br>}</pre><pre>DispatchQueue.global().async(group: group) {<br>  resource = 2<br>}</pre><pre>DispatchQueue.global().async(group: group) {<br>  resource = 3<br>}</pre><pre>group.notify(queue: .global()) {<br>  print(“Result = \(resource)”)<br>}</pre><p>Since we are using global queues, the order of calling setters is not guaranteed.</p><p>Result:</p><pre>3<br>2<br>1<br>Result = 1</pre><h4>Sync</h4><p>Let’s consider how we can restrict access to the data by multiple threads using the queues. This way I think is easier to read than the previous one. We use the sync method on serial queue for getter and setter and if you remember <a href="https://shchukin-alex.medium.com/gcd-queues-and-methods-f12453f529e7">the first article</a> it schedules the tasks one by one according to the FIFO principle. The function testQueueSynchronization tries to simulate the real-world scenario which can happen in the app, I mean the spreading of calling threads. For the all even i`th it schedules asyncAfter with the writing call at a random point of time in the range of 1 and 5 seconds from the current moment. And for the all-odd i`th we do the same but with the reading.</p><pre>let queue = DispatchQueue(label: “com.test.serial”)</pre><pre>private var internalResource: Int = 0<br>var resource: Int {<br>  get {<br>    queue.sync {<br>      print(“Read \(internalResource)”)<br>      sleep(1) // Imitation of long work<br>      return internalResource<br>    }<br>  }<br>  set {<br>    queue.sync {<br>      print(“Write \(newValue)”)<br>      sleep(1) // Imitation of long work<br>      internalResource = newValue<br>    }<br>  }<br>}</pre><pre>func testQueueSynchronization() {<br>  for i in 0..&lt;10 {<br>    if i % 2 == 0 {<br>      DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(Int.random(in: 1…5))) {<br>        self.resource = i<br>      }<br>    } else {<br>      DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(Int.random(in: 1…5))) {<br>        _ = self.resource<br>      }<br>    }<br>  }<br>}</pre><p>And the output in my run was (ofc it will be different for you):</p><pre>Write 4<br>Read 4<br>Read 4<br>Write 6<br>Read 6<br>Write 0<br>Read 0<br>Write 2<br>Read 2<br>Write 8</pre><h4>Barrier</h4><p>We can improve our previous solution using <em>barrier</em> flag which you can remember from <a href="https://shchukin-alex.medium.com/gcd-dispatchworkitem-and-quality-of-service-f206efdbf36f">the article about Quality of Service</a>. There we discussed barrier flag for the <strong><em>DispatchWorkItem</em></strong> and you will see that for queues it’s a similar logic. Queue with the Dispatch barrier is considered as one of the most effective ways of synchronization. Indeed it blocks the resource only on the writing but not on the reading. So we can build our asynchronous application in a way to minimize the blocking amount.</p><pre>let queue = DispatchQueue(label: “com.test.concurrent”, attributes: .concurrent)</pre><pre>private var internalResource: Int = 0<br>var resource: Int {<br>  get {<br>    queue.sync() {<br>      internalResource<br>    }<br>  }<br>  set {<br>    queue.async(flags: .barrier) {<br>      print(“ — — Barrier — -”)<br>      sleep(1) // Imitation of long work<br>      self.internalResource = newValue<br>    }<br>  }<br>}</pre><pre>func testBarrier() {<br>  for i in 0..&lt;10 {<br>    if i % 2 == 0 {<br>      DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(Int.random(in: 1…5))) {<br>        self.resource = i<br>      }<br>    } else {<br>      DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(Int.random(in: 1…5))) {<br>        print(self.resource)<br>      }<br>    }<br>  }<br>}</pre><p>In the example above we have the resource with a getter and setter. In the setter, we use a barrier flag to block it but in the getter, we use the sync method of the concurrent queue which is not blocking the resource for multiple threads.</p><p>The output of the testBarrier will be something like that:</p><pre>— — Barrier — -<br>6<br>6<br>6<br>— — Barrier — -<br>— — Barrier — -<br>— — Barrier — -<br>4<br>— — Barrier — -<br>8</pre><p>We can see that after the first barrier there are three reading calls happening at the same time.</p><p>That is it for today we’ve learned how we can use GCD constructions to provide thread safety in an application. <a href="https://shchukin-alex.medium.com/gcd-dispatchsource-and-target-queue-hierarchy-4c554ea10fd2">Next time we will discuss <strong><em>DispatchSource</em></strong> and its usage</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c74f13a800ca" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Part 3: DispatchGroup and concurrentPerform]]></title>
            <link>https://shchukin-alex.medium.com/gcd-dispatchgroup-and-concurrentperform-f0e52706748?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/f0e52706748</guid>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Thu, 16 Sep 2021 15:23:17 GMT</pubDate>
            <atom:updated>2022-10-20T19:07:38.569Z</atom:updated>
            <content:encoded><![CDATA[<p>Today we will consider one of the most useful GCD components <strong><em>DispatchGroup</em></strong> and also we will take a look at the <strong><em>concurrentPerform</em></strong> method and dispatch precondition.</p><h4>DispatchGroup</h4><p>In some cases, we need to follow a certain order of our tasks. To solve these issues we can use <strong><em>DispatchGroup</em></strong>. As you remember in <a href="https://shchukin-alex.medium.com/gcd-dispatchworkitem-and-quality-of-service-f206efdbf36f">the previous article</a> we learned how to use <strong><em>DispatchWorkItem</em></strong>. Some of the mechanics we used there are kind of similar to the group mechanics. In the example below, <strong><em>DispatchGroup</em></strong> is created and passed as a parameter to the method <strong><em>async</em></strong> of the concurrent queue. When all the tasks in the group are completed the <strong><em>notify</em></strong> method is called.</p><pre>let concurrentQueue = DispatchQueue(label: “com.test.concurrentQueue”, attributes: .concurrent)<br>let group = DispatchGroup()</pre><pre>concurrentQueue.async(group: group) {<br>  sleep(1)<br>  print(“test1”)<br>}</pre><pre>concurrentQueue.async(group: group) {<br>  sleep(2)<br>  print(“test2”)<br>}</pre><pre>group.notify(queue: DispatchQueue.main) {<br>  print(“All tasks completed”)<br>}</pre><p>Result:</p><pre>test1<br>test2<br>All tasks completed</pre><p>Other useful methods are <strong><em>enter</em></strong>,<strong><em> leave </em></strong>and<strong><em> wait</em></strong>. We can use them to make an order of the tasks’ execution. In the example below, we block the calling thread using method <strong><em>wait</em></strong> until all the tasks that were added to the group through method <strong><em>enter</em></strong> are marked finished through method <strong><em>leave</em></strong>.</p><pre>group.enter()<br>concurrentQueue.async {<br>  print(“test1”)<br>  group.leave()<br>}</pre><pre>group.enter()<br>concurrentQueue.async {<br>  print(“test2”)<br>  group.leave()<br>}</pre><pre>group.wait()<br>print(“All tasks completed”)</pre><p>Result:</p><pre>test1<br>test2<br>All tasks completed</pre><h4>ConcurrentPerform</h4><p>Sometimes we need to split our task into small chunks and execute them in parallel. In that case, Apple developers recommend us to use the <strong><em>concurrentPerform</em></strong> method instead of the calling method <strong><em>async</em></strong> of the concurrent queue in a cycle. It’s more efficient since GCD manages the optimization of the thread usage itself and avoids <em>thread explosion</em> which can be caused by frequent usage of the concurrent queue.</p><pre>DispatchQueue.concurrentPerform(iterations: 12) { _ in<br>// Execute part of the task<br>}</pre><p>Let’s consider a more complicated example. I want to use a heavy computed task to show the difference between the <strong><em>concurrentPerform</em></strong> method and the usual for-loop with concurrent async. For that goal, I chose the recursive Fibonacci sequence algorithm because its complexity is exponential (2^n) and on other hand, it’s pretty simple. In the code snippet below you can find the computation of the n`th element in the Fibonacci sequence:</p><pre>func fibonacci(n: Int) -&gt; Int {<br>  if n &lt;= 1 {<br>    return n<br>  }<br>  return fibonacci(n: n — 1) + fibonacci(n: n — 2)<br>}</pre><p>Here we have input values for this function — it’s generated with random numbers from a certain range:</p><pre>// It will produce something like these: [40, 39, 38, 36, 36, 37, 40, 35]</pre><pre>let parameters: [Int] = (0..&lt;8).map { _ in Int.random(in: 35…42) }</pre><p>So we need to calculate a Fibonacci n`th for each parameter from this array. We will start with <strong><em>concurrentPerform</em></strong> implementation:</p><pre>func concurrentPerformFibonacci() {<br>  DispatchQueue.concurrentPerform(iterations: parameters.count) { i in<br>    _ = fibonacci(n: parameters[i])<br>  }<br>}</pre><p>Now we need <strong><em>DispatchGroup</em></strong> skills we learned in the previous section:</p><pre>func asyncFibonacci() {<br>  let group = DispatchGroup()<br>  for i in 0..&lt;parameters.count {<br>    group.enter()<br>    self.concurrentQueue.async {<br>      _ = self.fibonacci(n: self.parameters[i])<br>      group.leave()<br>    }<br>  }<br>  group.wait()<br>}</pre><p>Here we use <strong><em>DispatchGroup</em></strong> to wait for all the tasks we added to <em>concurrentQueue</em>. As we can see the logic behind the implementation is similar to the <strong><em>concurrentPerform</em></strong> example. I’ve written simple measuring tests which can help us to analyze the performance gain we can get using the <strong><em>concurrentPerform</em></strong> method. Results you can find below:</p><pre>- concurrentPerform implementation:<br>3.385977029800415<br>3.1161649227142334<br>3.401739001274109<br>3.1878209114074707<br>3.072145104408264<br>3.2597930431365967<br>2.9462549686431885<br>2.918246030807495<br>4.10894501209259<br>7.421194911003113<br>Average time for concurrentPerform — 3.6818280935287477</pre><pre>- dispatchGroup implementation:<br>3.637176036834717<br>4.1981329917907715<br>3.9208900928497314<br>4.213144063949585<br>3.832044005393982<br>3.776208996772766<br>3.830193042755127<br>3.793861985206604<br>3.772049903869629<br>3.811164975166321<br>Average time for dispatchGroup — 3.8784866094589234</pre><p>All the provided measurements are displayed in seconds.</p><p>So we can see that <strong><em>concurrentPerform</em></strong> calculations are approximately faster by 20% than <strong><em>DispatchGroup</em></strong> ones most of the time except for the last couple of calculations for <strong><em>concurrentPerform</em></strong>. In these two cases, we can see peak values like 4.1 and 7.4. Why did it happen is a good question. My guess is it could be related to the fact it happened at the end of the measurement as the last two examples and the priority of the calculation were passed to some system jobs.</p><p>Results may vary depending on the system state like how it’s loaded with other tasks and threads but we can see that in general concurrentPerform 15–25% faster than <strong><em>DispathcGroup</em></strong> implementation.</p><h4>Dispatch precondition</h4><p>Another useful instrument we take a look at is <strong><em>dispatchPrecondition</em></strong>. It has similar logic to the asserts in swift. Basically, it prevents the execution of the task if the queue doesn’t follow certain conditions. In the example below, we want to be sure that the code will be executed only on the main queue. That can be useful if we want to work with UI.</p><pre>DispatchQueue.global().async {<br>  dispatchPrecondition(condition: .onQueue(.main))<br>  print(“test”)<br>}</pre><p>So as result you’ll probably see an error similar to mine:</p><pre>Thread 2: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)</pre><p>Or we don’t want to use global queues for some heavy logic we have (as was mentioned before we should try to avoid using global queues because active usage of global queues can cause the thread explosion). Here is how we can prevent that:</p><pre>DispatchQueue.global().async {<br>  dispatchPrecondition(condition: .notOnQueue(.global()))<br>  print(“test”)<br>}</pre><p>It will be the same error that you’ve seen in the previous example.</p><p>Here is another example you can use in practice. For example, we do not want to overload the main queue with calculations (you know we need to be super careful when we execute tasks on the main queue).</p><pre>DispatchQueue.global().async {<br>  dispatchPrecondition(condition: .notOnQueue(.main))<br>  print(“test”)<br>}</pre><p>Result:</p><pre>test</pre><p>Today we learned how to use DispatchGroup, measure concurrentPerform, and discover dispatchPrecondition. <a href="https://shchukin-alex.medium.com/gcd-synchronization-c74f13a800ca">Next time we will consider different ways of thread synchronization using gcd</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f0e52706748" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Part 2: DispatchWorkItem and Quality of Service]]></title>
            <link>https://shchukin-alex.medium.com/gcd-dispatchworkitem-and-quality-of-service-f206efdbf36f?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/f206efdbf36f</guid>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[gcd]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Fri, 02 Jul 2021 15:13:20 GMT</pubDate>
            <atom:updated>2022-10-20T19:02:03.763Z</atom:updated>
            <content:encoded><![CDATA[<p>This is the second part of the GCD series and here we will discuss <strong><em>QoS</em></strong> and <strong><em>DispatchWorkItem</em></strong>.</p><h4><strong>DispatchWorkItem</strong></h4><p>There is a way to add a task to the queue through a special class called <strong><em>DispatchWorkItem</em></strong> instead of direct passing the closure to <em>async</em> or <em>sync</em> methods. This class provides additional methods to interact with the task. For example, sometimes it is necessary to receive a completion notification. In that case, we need to call method <em>notify</em> and pass the completion block. We also need to specify in which queue (in the example below it’s the main queue) the completion will be executed.</p><pre>let item = DispatchWorkItem {<br>  print(“test”)<br>}</pre><pre>item.notify(queue: DispatchQueue.main) {<br>  print(“finish”)<br>}</pre><pre>serialQueue.async(execute: item)</pre><p>Result:</p><pre>test<br>finish</pre><p>We can also execute <strong><em>DispatchWorkItem</em></strong> manually using <em>perform</em> method:</p><pre>let workItem = DispatchWorkItem {<br>  print(“test”)<br>}</pre><pre>workItem.perform()</pre><p>Another useful case for <strong><em>DispatchWorkItem</em></strong> is the ability to cancel tasks through the <em>cancel</em> method in <strong><em>DispatchWorkItem</em></strong>. But there is a limitation: the cancellation will work only if the task has not started yet. Let’s how it works in the example below:</p><pre>serialQueue.async {<br>  print(“test1”)<br>  sleep(1)<br>}</pre><pre>serialQueue.async {<br>  print(“test2”)<br>  sleep(1)<br>}</pre><pre>let item = DispatchWorkItem {<br>  print(“test”)<br>}</pre><pre>serialQueue.async(execute: item)</pre><pre>item.cancel()</pre><p>Result:</p><pre>test1<br>&lt;- 1 second wait time -&gt;<br>test2</pre><p>Method <em>wait </em>is<em> a </em>very useful method. It blocks the calling thread until <strong><em>DispatchWorkItem</em></strong> finishes its task. Remember that’s not a good idea to call the method <strong><em>wait</em></strong> on the main thread. <strong><em>DispatchGroup </em></strong>has similar functionality and we will discuss it in <a href="https://shchukin-alex.medium.com/gcd-dispatchgroup-and-concurrentperform-f0e52706748">the next article</a>.</p><pre>let workItem = DispatchWorkItem {<br>  print(“test1”)<br>  sleep(1)<br>}</pre><pre>serialQueue.async(execute: workItem)<br>workItem.wait()<br>print(“test2”)</pre><p>Result:</p><pre>test1<br>&lt;- 1 second wait time -&gt;<br>test2</pre><p>There are plenty of flags you can set in the init of <strong><em>DispatchWorkItem</em></strong> most of them related to QoS but one can be considered out of the QoS context. It’s called barrier and it’s actually pretty similar to other barriers functionality we consider in this series. The key idea is that the work item is created with this parameter and added to the concurrent queue will wait until all the tasks in that queue will be finished and will block the execution of others until it will not finish. For a better understanding let’s check how it works in the example below:</p><pre>let concurrentQueue = DispatchQueue(label: “com.test.concurrent”, attributes: .concurrent)</pre><pre>let workItem = DispatchWorkItem(flags: .barrier) {<br>  print(“test2”)<br>  sleep(3)<br>}</pre><pre>concurrentQueue.async {<br>  print(“test1”)<br>  sleep(3)<br>}</pre><pre>concurrentQueue.async(execute: workItem)</pre><pre>concurrentQueue.async {<br>  print(“test3”)<br>}</pre><p>Result:</p><pre>test1<br>&lt;- 3 seconds -&gt;<br>test2<br>&lt;- 3 seconds -&gt;<br>test3</pre><h3><strong>QoS</strong></h3><p>In modern apps, we as developers usually try to find some balance between performance and battery usage. Since we work in a concurrent environment we need to prioritize some of our tasks based on their importance. For example, the user clicks a button and an animation should be displayed. In that case, we want a high prioritization of the rendering task. Or another example, we want to run some cleanup task of removing temporary files and the user shouldn’t receive any updates from this task so we can say that is a low-prioritized issue.</p><p>Quality of service is a single abstract parameter you can use to classify your work by its importance. There are four types of quality of service: <em>userInteractive</em>, <em>userInitiated</em>, <em>utility</em> and <em>background</em>. For the high-priority task, the application spends much more energy since it consumes more resources and for the low priority task, it spends lower energy.</p><p><em>userInteractive</em> — for tasks based on the user interaction like the refreshing user interface or performing rendering. The main thread of the application always comes with <em>userInteractive</em> mode.</p><p><em>userInitiated</em> — for tasks initiated by the user and required immediate result like the user clicks on the UI element and expects a quick response.</p><p><em>utility</em> — for tasks doesn’t require immediate result but the user needs to be updated like downloading task with the progress bar.</p><p><em>background</em> — for tasks that are not visible to the user like synchronizing or cleaning tasks.</p><p>There are two additional QoS classes <em>default</em> and <em>unspecified</em> that developers should not use directly</p><p><em>default</em> — according to Apple documentation, the priority level of this QoS is between <em>userInitiated</em> and <em>utility</em>.</p><p><em>unspecified</em> — means the absence of information about QoS and expects it will be propagated (we will explore this in the next paragraph).</p><p>Interesting fact, for the global queue, if we don’t specify QoS the value should be <em>default</em>:</p><pre>class func global(qos: DispatchQoS.QoSClass = .default) -&gt; DispatchQueue</pre><p>But in fact, it has <em>unspecified</em> value:</p><pre>print(DispatchQueue.global().qos.qosClass)<br>print(DispatchQueue.global(qos: .background).qos.qosClass)</pre><p>Result:</p><pre>unspecified<br>background</pre><h4><strong>QoS propagation</strong></h4><p>Another important thing to understand is how QoS can be propagated between queues. As I mentioned before the main thread is associated with <em>userInteractive</em> value. That means all the tasks you are executing on the main thread will take the highest priority.</p><pre>DispatchQueue.main.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>}</pre><p>Result:</p><pre>userInteractive</pre><p>In case when we don’t specify QoS directly in the queue it acquires the QoS from the calling thread. As you can see in the example below we don’t specify the QoS for the serial queue and it captures it automatically from the calling utility queue. This mechanic is called automatic propagation.</p><pre>let serialQueue = DispatchQueue(label: “com.test.serial”)<br>let utilityQueue = DispatchQueue(label: “com.test.utility”, qos: .utility)</pre><pre>utilityQueue.async {<br>  serialQueue.async {<br>    print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>  }<br>}</pre><p>Result:</p><pre>utility</pre><p>There is one important exception for the previous rule — if we add the task to the queue from the <em>userInteractive</em> thread (or main thread) it automatically drops from <em>userInteractive</em> to <em>userInitiated</em>.</p><pre>DispatchQueue.main.async {<br>  serialQueue.async {<br>    print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>  }<br>}</pre><p>Result:</p><pre>userInitiated</pre><p>Also, this rule doesn’t work backward. It means if we call from the low-priority thread the high-priority task it keeps its own high priority. In the example below the calling queue (utilityQueue) has low priority compared to the called queue (userInitiatedQueue) so the task of the called queue is to be executed in <em>userInitiated</em> mode.</p><pre>utilityQueue.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)</pre><pre>  userInitiatedQueue.async {<br>    print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>  }<br>}</pre><p>Result:</p><pre>utility<br>userInitiated</pre><p>Let’s consider the case when we need directly to specify the QoS of the executing task. To do that we can set QoS as a parameter for <em>async</em> or <em>sync</em> methods of the <strong><em>serialQueue</em></strong> we created before. Or we can associate the queue with a specific QoS and set it as a parameter on its creation.</p><pre>let serialQueue = DispatchQueue(label: “com.test.serial”)</pre><pre>serialQueue.async(qos: .utility) {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>}</pre><pre>// Or</pre><pre>let utilityQueue = DispatchQueue(label: “com.test.utility”, qos: .utility)</pre><pre>utilityQueue.async {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>}</pre><p>Result:</p><pre>utility</pre><p>Ok now we know how to use QoS with the queues but there are more sophisticated cases with <strong><em>DispatchWorkItem</em></strong>. Using the flags parameter in the init we can define how QoS will be propagated to the task (or not). The first flag we consider is called <em>inheritQoS</em> it means that the executed task will prefer to assign QoS from the calling thread.</p><pre>utilityQueue.async {</pre><pre>let workItem = DispatchWorkItem(qos: .userInitiated, flags: .inheritQoS) {</pre><pre>print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)</pre><pre>}</pre><pre>workItem.perform()</pre><pre>}</pre><pre>// Or</pre><pre>let workItem = DispatchWorkItem(qos: .userInitiated, flags: .inheritQoS) {</pre><pre>print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)</pre><pre>}</pre><pre>utilityQueue.async(execute: workItem)</pre><p>Result:</p><pre>utility</pre><p>Another flag is called <em>enforceQoS</em> and has reverse functionality with the previous one. In this case, the task will acquire QoS from the <strong><em>DispatchWorkItem</em></strong>.</p><pre>let workItem = DispatchWorkItem(qos: .userInitiated, flags: .enforceQoS) {<br>  print(DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .unspecified)<br>}</pre><pre>utilityQueue.async(execute: workItem)</pre><p>Result:</p><pre>userInitiated</pre><p>There is one important addition to that functionality. Let’s say we have a serial queue and its QoS is <em>utility</em> and there is already a task added to the queue (since it doesn’t have any flags it also has QoS <em>utility</em>). This situation causes the Priority Inversion and GCD automatically resolves it raising the QoS of the low-prioritized task. That is not visible to the developer since it’s caused by GCD. But ofc we need to keep it in mind by developing concurrent applications.</p><pre>utilityQueue.async {<br>  sleep(2)<br>}</pre><pre>let workItem = DispatchWorkItem(qos: .userInitiated, flags: .enforceQoS) {<br>  sleep(1)<br>}</pre><pre>utilityQueue.async(execute: workItem)</pre><p>So in this part, we discussed pretty complicated moments related to QoS. In <a href="https://shchukin-alex.medium.com/gcd-dispatchgroup-and-concurrentperform-f0e52706748">the next article, we will look at <strong><em>DispatchGroup</em></strong> and ways to work with it</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f206efdbf36f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCD Part 1: Queues and methods]]></title>
            <link>https://shchukin-alex.medium.com/gcd-queues-and-methods-f12453f529e7?source=rss-dac92733704c------2</link>
            <guid isPermaLink="false">https://medium.com/p/f12453f529e7</guid>
            <category><![CDATA[gcd]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Alex Shchukin]]></dc:creator>
            <pubDate>Wed, 26 May 2021 14:56:23 GMT</pubDate>
            <atom:updated>2022-10-20T18:55:15.647Z</atom:updated>
            <content:encoded><![CDATA[<p>I would like to start a series of articles about Grand Central Dispatch (GCD). GCD or libdispatch is one the most popular instruments for multithreading programming in iOS and macOS. It’s a library written in C to ease thread management. Instead of the manual creation of threads and their subsequent control, we can use abstract queues and put all the responsibility of thread management on them.</p><p>In the series, we will cover basic primitives like queues, how to work with them, research dispatch source, and touch on DispatchIO (which is not a super popular tool). We will try to implement some basic approaches that we can use in real-world applications. And for the most curious, we will try to implement GCD primitives ourselves.</p><p><strong>Dispatch queues</strong></p><p>In this first article, I’ll explain dispatch queues and how to work with them. Basically, a queue is based on the same principles as FIFO queue (one of the classical data structure primitives).</p><p>Here is how we can create a serial queue. As shown in the code below, a serial queue is created by default without any specification.</p><pre>let serialQueue = DispatchQueue(label: “com.test.serial”)</pre><p>In contrast, a concurrent queue executes in parallel. You create the concurrent queue by setting <strong><em>attributes</em></strong> parameter to <strong><em>concurrent</em></strong>.</p><pre>let concurrentQueue = DispatchQueue(label: “com.test.concurrent”, attributes: .concurrent)</pre><p>It’s important to understand the relation between queues and threads. First of all, a queue is an abstraction around the threads. There is a thread pool that is used by queues so each queue performs its tasks on the threads from that thread pool. A serial queue is limited by using only one arbitrary thread and in contrast, a concurrent queue is available to use multiple threads for its tasks. Let’s consider a situation where we split our work into different pieces and run them on the concurrent queue. A concurrent queue will execute the tasks in the different threads. Since that the core can perform only one thread at a time we are quite limited in terms of parallel execution. This case is called <strong>Thread explosion</strong>. It’s very heavy performance-wise and in the worst case, it can cause deadlock. That means we should be very careful with the usage of the concurrent queues and do not overload them with a big amount of tasks. Another very good practice is to limit the number of serial queues and use <strong>target queue hierarchy</strong> per subsystem. We will take a close look at the target queue hierarchy in <a href="https://shchukin-alex.medium.com/gcd-dispatchsource-and-target-queue-hierarchy-4c554ea10fd2">the following article</a>.</p><p>The <strong><em>label</em></strong> parameter used in both scenarios is a unique string identifier. It helps to find the queue in different debug tools. Since GCD queues are used through different frameworks, it is recommended you choose a reverse-DNS style.</p><p>There is also a possibility to fetch a queue from a pool of queues. These queues are created by an OS and can be used for system tasks. For heavy tasks, it is better to create your own queues instead of using global ones.</p><pre>let globalQueue = DispatchQueue.global()</pre><p>All global queues are concurrent but there is one exception in that rule — the main queue. This queue is serial and all the tasks that are queued on it are executed in the main thread.</p><pre>let mainQueue = DispatchQueue.main</pre><p><strong>Async vs Sync</strong></p><p>Let’s discuss how to use queues. Async and sync are two basic methods that we can use to interact with queues. Sync waits until the task finishes and async returns control of execution after it starts the task. In the example below, you can see how async and sync work for different types of queues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0J0Uemy38X0a0z9oxXKO2g.png" /></figure><p>The serial queue:</p><pre>serialQueue.async {<br>   print(&quot;test1&quot;)<br>}</pre><pre>serialQueue.async {<br>   sleep(1)<br>   print(&quot;test2&quot;)<br>}</pre><pre>serialQueue.sync {<br>   print(&quot;test3&quot;)<br>}</pre><pre>serialQueue.sync {<br>   print(&quot;test4&quot;)<br>}</pre><p>Result:</p><pre>test1<br>test2<br>test3<br>test4</pre><p>Let’s consider what happens if you try to call the <em>sync</em> method inside of the <em>sync</em> method in the same <strong>serial</strong> queue. The task will be added to the queue and the queue will wait until the task is finished. Inside the task, another sync block will be caused. But it will not start until the serial queue finishes the current task. So we are coming to a situation where the tasks block each other. This situation is called deadlock and we will look at it in the following articles.</p><pre>// Cause deadlock<br>serialQueue.sync {<br>   serialQueue.sync {<br>      print(“test”)<br>   }<br>}</pre><p>Ok, since the main queue is a serial queue we come to another rule — you should not call sync from the main queue in the main thread. The idea is pretty much the same as in the previous paragraph. Task called from the main queue awaits because the main queue can’t finish the current task.</p><pre>// Cause deadlock<br>DispatchQueue.main.sync {<br>   print(“test”)<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M58OO_ic92ch0N2hCjv1jA.png" /></figure><p>In the concurrent queue example we can only guarantee that <strong><em>test3</em></strong> will be printed after <strong><em>test4</em></strong>:</p><pre>concurrentQueue.async {<br>   print(“test1”)<br>}</pre><pre>concurrentQueue.async {<br>   print(“test2”)<br>}</pre><pre>concurrentQueue.sync {<br>   print(“test3”)<br>}</pre><pre>concurrentQueue.sync {<br>   print(“test4”)<br>}</pre><p>Result:</p><pre>test2<br>test1<br>test3<br>test4</pre><p>or</p><pre>test1<br>test3<br>test2<br>test4</pre><p>As you can see the order of the printing is arbitrary except that test3 will be printed before test4.</p><p><strong>Method asyncAfter</strong></p><p>If we want to delay the execution of the task we can use another method called <strong>asyncAfter</strong>. This method returns the control of the execution to the calling thread and will execute the task at a certain moment in time. In the example below the task will be executed 3 seconds after it is added to the queue.</p><pre>concurrentQueue.asyncAfter(deadline: .now() + 3, execute: {<br>   print(“test”)<br>})</pre><p>Result:</p><pre>&lt;- 3 seconds wait time -&gt;<br>test</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eJjS-C5SYUg3Kfi3HV0n2g.png" /></figure><p>Let’s consider another situation where we execute a long-term task on a serial queue. So we schedule the task for a certain period of time but the long-term task is not finished yet. In this scenario, <strong>asyncAfter</strong> will wait until the long-term task finishes then will execute its tasks thereafter.</p><pre>serialQueue.async {<br>   sleep(3)<br>   print(“finish”)<br>}</pre><pre>serialQueue.asyncAfter(deadline: .now() + 1, execute: {<br>   print(“test”)<br>})</pre><p>Result:</p><pre>&lt;- 3 seconds wait time -&gt;<br>finish<br>test</pre><p>Ok, we learned what are the basic primitives (the queues) in GCD and how to use them. It’s very important to understand these concepts because all the GCD functionalities are based on them. It was the first part of the series of articles and in the <a href="https://shchukin-alex.medium.com/gcd-dispatchworkitem-and-quality-of-service-f206efdbf36f">next article, we will look at QOS(quality of service)</a>. I’ll explain how it works and we will run some examples.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f12453f529e7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>