Skip to content

Latest commit

 

History

History
421 lines (263 loc) · 19.8 KB

File metadata and controls

421 lines (263 loc) · 19.8 KB

Concurrency & Parallelism

Learning Objectives

By the end of this lesson, you should be able to...

  1. Describe:
  • Why Concurrency is important in iOS
  • The relationships between processes, threads, and tasks, and how they fit together at the launch of any iOS application
  • The similarities and key differences between Parallelism and Concurrency
  • How you could have Concurrency without Parallelism and vice-versa
  • (at a very high level) Apple's primary API for managing Concurrency - Grand Central Dispatch (GCD)
  1. Identify:
  • the five major challenges associated with Concurrency

Why you should know this

Q: Why do apps need concurrent activities?

A: To keep the UI responsive.

When you create a new iOS app, the app acquires its main thread. That main thread is responsible for running all of the code that powers the app's user interface.

As you add code on your main thread to perform large items of non-UI work — such as image processing or fetching and transforming data — you will find that your UI's performance suffers drastically.

Your user interface will slow down, or maybe even stop altogether.

A common example:

  • A table view that will not scroll properly while the app is downloading and transforming images; scrolling stutters and you might need to display multiple "busy" indicators of the expected images.

The concept of Concurrency in iOS is about how to structure your app to avoid such UI performance issues by directing slow, non-UI tasks to run somewhere other than on the UI thread (aka, the main thread).

Concurrency issues loom large in any list of the top mistakes made by iOS developers. They are also the underlying cause of the majority of negative app reviews.

Thus it is not surprising that questions on iOS concurrency are now a standard part of the technical interview process for iOS development jobs.

Overview/TT I

Terms & Concepts

Key concepts covered in this course will include:

  • Process
  • Thread
  • Task
  • Multi-Core Systems
  • Concurrency
  • Parallelism
  • Queues (Serial, Concurrent)
  • Synchronous vs Asynchronous
  • Grand Central Dispatch (GCD)
  • Background Tasks
  • Quality of Service (QoS)
  • Operations
  • Dispatch Groups
  • Semaphores
  • Debugging
  • Testing Strategies

...we will cover a few of the most essential concepts today...and the rest, we'll cover later in the course...

Processes & Threads

Process — The runtime instance of an application. A process has its own virtual memory space (aka, virtual machine) and system resources (including port rights) that are independent of those assigned to other programs.

  • A process always contains at least one thread (the main thread) and may contain any number of additional threads.

Thread — A flow of execution inside a process. A thread of execution is the smallest sequence of programmed instructions that can be managed independently by the operating system's scheduler.

  • Each thread comes with its own stack space but otherwise shares memory with other threads in the same process.

  • A thread defines a discrete mechanism, within a single process, for executing tasks.

  • Threads can execute concurrently, but that is up to the operating system.

Comparing Processes to Threads

Processes Threads
Are typically independent Threads exist as subsets of a process
Have separate address spaces Threads share their address space with other threads in the same process
Carry considerably more state information than threads Multiple threads within a process share process state as well as memory and other resources

Tasks

Task — A quantity of work to be performed.

A task is simply some work that your application needs to perform (i.e., some block of code to execute).

For examples, you could create a task to perform some calculations, blur an image, create or modify a data structure, process some data read from a file, convert JSON data, or fetch data from local/remote sources.

Sources:

  • Wikipedia
  • Apple Concurrency Programming

Where do tasks run?

Tasks run on threads...

  • The UI (and all UI-related tasks) runs on the Main thread, which is automatically created by the system.
  • The system also creates other threads for its own tasks. Your app can use these threads...or create its own threads.

Parallel Computing (Parallelism)

Parallel programming utilizes a shift from procedural tasks, which run sequentially, to tasks that run at the same time.

In Parallel Computing:

  • Many calculations or the execution of processes are carried out simultaneously.

  • A computational task is typically broken down into several very similar sub-tasks that can be processed independently and whose results are combined after all tasks are completed.

Note that there are several different forms of Parallel Computing: bit-level, instruction-level, data, and task parallelism.

Concurrency

Concurrency refers to the ability to decompose a program, algorithm, or problem into smaller components or units that can be executed out-of-order, or in partial order, without affecting the final outcome.

Concurrency is the act of dividing up work.

This allows for parallel execution of the concurrent units, which can significantly improve overall speed of execution on multi-processor and multi-core systems.

Multiple Processors / Cores

A recent trend in computer architecture is to produce chips with multiple cores (CPUs) on a single chip, a trend driven by concerns over excessive heat generated by increased power consumption.

With the advent of modern multi-core CPUs, Parallel Computing has become the dominant paradigm in computer architecture due to its potential to optimize performance.

Multi-core devices execute multiple threads at the same time via Parallelism.

                               414px-Dual_Core_Generic.svg

          Diagram of a generic dual-core processor with CPU-local level-1 caches
          and a shared, on-die level-2 cache.

          Source:
          CountingPine at English Wikipedia - Public Domain,
          https://commons.wikimedia.org/w/index.php?curid=11894458

Are Parallelism and Concurrency the same thing?

Parallel Computing is closely related to Concurrent Computing (in fact, Concurrent Computing is an example of task parallelism.)

Concurrency is about structure, while Parallelism is about execution.

Though both are frequently used together, and often conflated, the two concepts are distinct:

  • it is possible to have parallelism without concurrency (such as bit-level parallelism)
  • it is also possible to have concurrency without parallelism (such as multitasking by time-sharing on a single-core CPU).

What does it mean for a task to run concurrently?

Tasks run on threads.

But for threads to execute tasks concurrently, must multiple threads run at the same time?

Single-core devices can achieve Concurrency through time-slicing, in which the OS uses "context switching" to alternate between multiple threads.

For a multi-threaded application running on a traditional single-core chip, the OS would run one thread, perform a context switch, then run another thread, as illustrated in the first diagram below where thread 1 (T1) pauses while threads 2 thru 4 run, then thread 1 resumes, etc.:


figure_4.3


On a multi-core chip, the threads could be spread across all available cores, allowing true parallel processing, as shown here:


figure_4.4

Source:
https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/4_Threads.html

In Class Activity I (30 min)

As A Class

     Let's play the Movie Theatre Game...

Overview/TT II

Concurrency on iOS

How many cores on an iOS device?

There can be as many threads executing at once as there are cores in a device's CPU.

iPhones and iPads have been dual-core since 2011, with more recent models boasting as many as 12 cores per chip (see 1).

With more than one core available, iOS apps are capable of running more than a single task at the same time. (Potentially, up to 8 tasks simultaneously, though this again is ultimately up to the operating system).

Anatomy of a running iOS app

Below is a simplified diagram of the structure inside the runtime process (aka, virtual machine) of an iOS app at launch: the moment the user taps the app icon.

(1) When an iOS app starts, the system automatically creates the app's main thread and the corresponding call stack that the main thread manages.

(2) The main thread eventually (after executing required Cocoa Touch functions) allocates your app's Application object in its stack frame, which in turn executes its delegate methods on its AppDelegate object in their respective stack frames, then the AppDelegate begins creating all of your app's user interface components and behavior.

From that point on — and until the Application object's lifecycle (run loop) ends — all UI-related code in your app will execute on the main thread.

  • This behavior ensures that user-related events are processed serially in the order in which they were received.

  • But unless specified otherwise, all non-UI code will also execute on the main thread (exceptions to this include frameworks such as URLSession in which some tasks run on non-UI threads by default).

(3) Meanwhile the system also creates additional threads (nonUI threads), along with their corresponding call stacks, making them available for use by your app.


      iOS_runtime_process

How to apply Concurrency?

Splitting your app into logical "chunks" of code enables iOS to run multiple parts of your app across more than one core at the same time, which can greatly improve overall performance.

In general, look for opportunities to structure your apps so that some tasks can run simultaneously: Determine which pieces can run at the same time — and possibly in random order — yet still result in a correct implementation of your data flow for your users.

Tasks which are good candidates to run simultaneously from different threads typically fall into these categories:

  • tasks that access different resources
  • tasks that only read values from shared resources

Important Note: Tasks which modify the same resource must not run at the same time, unless the resource is threadsafe (we'll cover thread safety later in the course)

Introducing to GCD

Most modern programming languages provide some form of Concurrency, but different languages use widely disparate mechanisms for handling it.

Swift and iOS provide two APIs you can use to improve your app's performance through Concurrency:

  • Grand Central Dispatch — commonly known as GCD (also simply called "Dispatch" by Apple).
  • Operations — which are built on top of GCD.

What is GCD?

Grand Central Dispatch (GCD) is a low-level API for managing concurrent operations.

Named after Grand Central Station in New York City, GCD was released by Apple in 2009 to optimize application support for systems with multi-core processors and other symmetric multiprocessing systems.

It is an implementation of task parallelism based on the Thread Pool design pattern.

The fundamental idea is to move the management of the thread pool out of the hands of the developer, and closer to the operating system.

GCD offers you an efficient mechanism for executing code concurrently on multicore hardware by submitting work to dispatch queues managed by the system rather than working with threads directly.

In the next lessons, we will dig deeper into these two Apple concurrency frameworks, including learning more about the differences between GCD and Operations, as well as when to choose one over the other...

Challenges of Currency/Parallelism

By now, you've likely gotten the idea that Concurrency can significantly alleviate performance issues for you.

But it isn't free.

Concurrency presents its own specific development challenges.

In this course we will explore the following set of the most major challenges associated with Concurrency, along with standard approaches to avoid or resolve them:

  • Deadlocks
  • Race Conditions
  • Readers-Writers Problem
  • Thread Explosions
  • Priority Inversion

In Class Activity II (30 min)

Before we delve deeper into GCD in the next lesson, let's explore a simplified example of what implementing Concurrency without GCD might entail...

Part 1 - Individually

The code in the Threads.playground below is incomplete. It is intended to create a second instance of the Thread class named "Background Thread" that executes the calculation function/closure.

TODO: Complete the code to match the output listed below it:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let calculation = {
    for i in 0...100 {
        print(i)
    }
}

let thread = Thread {

    //TODO: What must the thread do here to match the expected output listed below?
}

print("On thread: \(Thread.current) doing nothing")
//TODO: Give new thread its proper name, as in expected output...
thread.qualityOfService = .userInitiated

thread.start()

/* EXPECTED OUTPUT:
 On thread: <NSThread: 0x6000022d28c0>{number = 1, name = main} doing nothing
 On thread: <NSThread: 0x6000022fba00>{number = 3, name = Background Thread} doing work
 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 ...
 100
 */

Part 2 - In Pairs

TODO: Trace down the source of the Foundation types Thread, Thread.current, and the .start() function.

  • Is it easy to infer how to implement these properties and functions?

Q: Listed below is a selected portion of the output:

  • What do the hexadecimal numbers next to <NSThread: tell us and how could that information be useful?
  • Where did the properties number and name come from?
  On thread: <NSThread: 0x600003af0dc0>{number = 1, name = main} doing nothing
  On thread: <NSThread: 0x600003acc340>{number = 3, name = Background Thread} doing work

Q: This approach involved direct creation and management of threads.

  • What drawbacks do you foresee with this approach, especially in more complex implementations?

After Class

  1. Research:
  • Task Parallelism
  • Bit-Level Parallelism
  • Amdahl's Law and Gustafson's Law
  • Call Stack, Stack Frames, and Stack Pointer
  • The Heap
  • Thread Pool design pattern
  • Scheduler (for iOS thread scheduling)
  • Run Loop
  • Async/Await pattern (and Swift 5.0)
  • Nonatomic (vs Atomic)
  • Dispatch Queues
  • Quality of Service (QoS) Priority - as defined by Apple for iOS/macOS

Wrap Up

  • Complete reading / research

Additional Resources

  1. Slides
  2. Parallel computing - wikipedia
  3. Concurrency (computer_science) - wikipedia
  4. Threads - an article
  5. Processes and Threads - Apple
  6. Apple-designed_processors - Apple 1
  7. Dispatch - from Apple
  8. Grand_Central_Dispatch - wikipedia 2
  9. The App LifeCycle - Apple
  10. Context switch - wikipedia
  11. Thread safety - wikipedia
  12. Call Stack