Multithreading in iOS-Part 1/4

gcd, thread, multi thread, process

Manasa M P
Jun 1 · 12 min read

The most important one which every user expects include execution of demanding tasks in less time, best user experience, no frozen user interfaces, and so on. Now a days every device support multiple CPU cores to achieve this. Multiple cores allow to run multiple processes at the same time. This can be achieved by using multiple thread.

What is process? A process is a program that is running on your computer.

What is Thread? A thread is sequence of instructions that can be executed by a runtime.

What is Multi Thread application? We could say that the application is multithreaded since it uses more than one thread.

What is use of Multi Thread? Multithreading is a pattern to increase the performance of an application and scheduling work on multiple threads, more work can be performed in parallel.

How can we achieve Multithreading?

Your device has multiple CPU cores. To make use of these cores, you will need to use multiple threads. Perform work in parallel is also known as concurrency.

Why do We Need Concurrency?

  • Utilize iOS devices’ hardware: Now all iOS devices have a multi-core processor that allows developers to execute multiple tasks in parallel. You should utilize this feature and get benefits out of the hardware.
  • For better user experience.

Creating multiple thread can be achieved by bellow three ways in iOS

  1. Creating and managing threads manually
  2. Using GCD (Grand Central Dispatch):
  3. Operations and queues (NSOperation)

Is there any disadvantage of using MultiThread?

when we compare with performance, multithreads are benefit but there are some disadvantages as well.

  1. Each thread takes some time during creation.
  2. It will take app and kernel space.
  3. Deadlock : multi thread can cause deadlock
  4. Priority Inversion
  5. Producer-Consumer Problem

Kernel Data Structures : Memory is used to store the data structures and attributes related to that thread. it consumes ~1KB of memory

Stack Size: Each thread has its own stack size. Main thread has 1MB stack size it can be less than that depending on use. secondary thread is allocated with ~524KB of stack space by default. Full stack is not created immediately. Actual stack size grows with use

Pint main and secondary thread stack size

You can set default stack size before the start of thread using stackSize . This value must be in bytes and a multiple of 4KB. Setting the stack size after the thread has started changes the attribute size

What is Main Thread?

Each process has at least one thread. In iOS, primary thread on which process is started is commonly called as Main thread

is used to check current running thread is main thread or not

Creating and managing threads Manually:

where it is left to the developer to manage all concurrency. Developer need to create and manage the thread.

You can Create thread by two ways

  1. We can create thread instance as follow: With the bellow method, thread will start executing immediately.
Thread.detachNewThreadSelector(<Selector, toTarget: , with:>)

2. Thread can be create by using init() method as follows: If you just initialise the thread then it will not execute the code immediately as above. you need to explicitly call thread.start() method to execute.

We can even call .cancel() in order to cancel the execution, perform(Selector, with:, afterDelay: TimeInterval, inModes: [RunLoop.Mode]) to perform action after delay.

Disadvantages of above method to create threads:

  1. Developers are responsible to create new threads and adjusting that number dynamically as system conditions change
  2. Manage them carefully, deallocating them from memory once they have finished executing
  3. Leverage synchronisation mechanisms like mutexes, lock, semaphore to orchestrate resource access between threads, adding even more overhead to application code
  4. Improper thread management may cause memory leak in app
  5. By default auto release pool is not there for the threads which we created and maintaining the order of execution is difficult

To solve these issues we have next way to create the threads using GCD (Grand Central Dispatch):

GCD (Grand Central Dispatch):

GCD is the abstract away for thread management code and makes working with threads easier and more efficient. It moves all thread creation and management work down to the system level.

It offers developers an API that facilitates the scheduling of tasks. Which thread is used to execute a task is handled by Grand Central Dispatch, not the developer and execute them on an appropriate dispatch queue.

What is Dispatch queue? Is the abstraction layer on top of the queue

GCD manages a collection of dispatch queues. They are usually referred as queues. The work submitted to these dispatch queues is executed on a pool of threads

GCD takes care of all thread creation, management and scheduling. It also helps to keep track of total number of threads in your app and it’ll not cause leak.

What Is a Queue? A queue is a block of code that can be executed synchronously or asynchronously either on main thread or on background thread

Once the queue is created, the operating system is the one that manages it and gives time to process on any core of the CPU. Multiple queues are managed accordingly and developer don’t have to deal with management.

Queues follows first in first order(FIFO), meaning that the queue that comes first for execution will also finish first.

Dispatch queue can be created as follows:

What is synchronous(sync) or asynchronous(async) task?

Synchronous basically means that you can execute one thing at a time and you have to finish executing the current thing in order to move on to next one. Asynchronous means that you can execute multiple things at a time and you don’t have to finish executing the current thing in order to move on to next one.

what is task? Tasks can refer to any block of code that you want to execute.

How can you achieve synchronous or asynchronous task?

This can be achieved by calling sync or async method using synchronous or asynchronous queue. This is what we call it as serial queue or concurrent queue.

sync and async call with global queues.

1. Serial Queue/Synchronous queue: It helps to execute synchronous task i.e one at a time.

It means that the thread that initiated that operation will wait for the task to finish before continuing.

Task will start in the order they’re added and they always finish in same order

The advantage of serial queue is predictability that means we have reference of which task executes first.

The disadvantage is decreased performance. The second task needs to wait until the first task has finished executing.

e.g for serial queue

2. Concurrent Queue/Asynchronous queue: Allows us to execute multiple task at a time.

Task will start in the order they’re added but they can finish in any order as they can be executed in parallel.

Task will run on distinct threads that are managed by dispatch queue. This completes a task in background and can notify you when it complete. no need to wait till task complete.

The advantage of concurrent queue is it can’t be predictability

The disadvantage is increase performance. multiple task can be execute in less time.

e.g for concurrent queue

Which one to use?

Serial queues take advantage of CPU optimisations and caching, and help reduce context switching. Apple recommends starting with one serial queue per subsystem in your app-for example one for networking, one for file compression, etc

Broadly speaking, GCD provides three kinds of queues which are available to you:

  1. The Main dispatch queue (serial, pre-defined)
  2. Global queues (concurrent, pre-defined)
  3. Private queues (can be serial or concurrent, you create them)

Main Queue and Global Queues:-

Every application has access to a main queue and several background queues.

the main queue is associated with the main thread. What does that mean? Work that is scheduled on the main queue is guaranteed to be executed on the main thread.

Grand Central Dispatch also manages a number of concurrent queues that perform work on a pool of background threads. These dispatch queues are also known as global queues.

This code will run on main thread
This code executes on global queues

global queue is concurrent by default and executes code by sync and async method

Private queues (can be serial or concurrent, you create them):-

you can create your own serial or concurrent dispatch queue with sync or async task as follows:

1. code 2. output
  • async — concurrent: the code runs on a background thread. Control returns immediately to the main thread. It will not block any updates to the UI. The block can’t assume that it’s the only block running on that queue
  • sync — concurrent: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can’t assume that it’s the only block running on that queue.
concurrent sync and async
  • async — serial: the code runs on a background thread. Control returns immediately to the main thread. It will not block any updates to the UI. The block can assume that it’s the only block running on that queue
  • sync — serial: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can assume that it’s the only block running on that queue.
serial sync and async

You can also create private queue as follows:-

Parameters:-

Label : defines the name of the queue. It helps to identify queue in debug tool like instruments, crash report. It is recommended that we use a reverse DNS naming convention

qos (quality of service) : This value determines the priority at which the system schedules tasks for execution. Types are User-interactive,User-initiated, Utility, Background. Higher the priority, higher will be the allocation of resources to that queue.

  1. User-interactive:-
  • This represents tasks that need to be done immediately in order to provide a nice user experience.
  • Use it for UI updates, event handling and small workloads that require low latency.
  • The total amount of work done in this class during the execution of your app should be small.
  • This should run on the main thread.

2. User-initiated:-

  • The represents tasks that are initiated from the UI and can be performed asynchronously
  • It should be used when the user is waiting for immediate results, and for tasks required to continue user interaction.
  • This will get mapped into the high priority global queue.

3. Utility:-

  • This represents long-running tasks, typically with a user-visible progress indicator
  • Use it for computations, I/O, networking, continuous data feeds
  • This class is designed to be energy efficient.
  • This will get mapped into the low priority global queue.

4. Background:-

  • This represents tasks that the user is not directly aware of.
  • Use it for prefetching, maintenance, and other tasks that don’t require user interaction
  • This will get mapped into the background priority global queue.

5. Default:-

  • The priority level of this QoS falls between user-initiated and utility.
  • Work that has no QoS information assigned is treated as default, and the GCD global queue runs at this level.

6. Unspecified:-

  • This represents the absence of QoS information

background has the lowest priority and userInteractive has the highest in qos

mapping priority with qos class

highest priority qos task completes first then lowest qos task finishes its execution.

Background qos has lowest priority when it is compared with userInteractive qos so userinteractive block finishes execution first then background task.

attributes: It include the concurrent attribute to create a dispatch queue that executes tasks concurrently or it has value of initiallyInactive which says queue is initially inactive. It will become active once active() is called.

If the this parameter is nil, a serial queue will be created.

two option in attributes
1. queue is not active 2. queue is active

qos for main thread is always userInteractive and you can set the priority for global queue as follows

autoreleaseFrequency: The frequency with which to autorelease objects created by the blocks that the queue schedules.

DispatchQueue.AutoreleaseFrequency.never doesn’t set up autorelease pool doesn’t mean that autorelease objects are never released, as GCD relies on Thread which have their own autorelease pool , but it’s still non-deterministic. This is the default value for global queues

Note: when you dispatching closures using Objective-C objects, you might want to explicitly add an autorelease pool if you see memory spikes related to GCD use.

Using autorelease pool does add some overhead, and allocating/freeing large objects might have a significant impact on performance, which is why it could be beneficial to release autoreleased objects all at once rather than one by one, but it depends on the use case.

target: The target queue on which to execute blocks. A dispatch queue’s priority is inherited from its target queue.

It has 3 types

  1. .main : .main will run on the main thread. The main thread is used primarily for UI work. This queue has the highest priority.
  2. .global() : is primarily used for other work that is not UI related. The global queue has three priorities Low, Default & High. This queue has the second highest priority with 3 different types.
  3. nil : nil is the lowest priority and will be lower than any global queue. It has no priority, it just needs to get done.

Target queue uses:

  1. If you set your custom queue’s target to be the low priority global queue, all work on your custom queue will execute with low priority and the same with the high priority global queue.
  2. To target a custom queue to the main queue. This will execute all code block which submitted to that block will execute on main thread.
  3. To target custom queues for other custom queues. It will create a group of queues and multiple queues to be serialised with respect to each other. It helps to suspend and resume together by suspending/resuming the queue that they target.

Advantages of GCD:

  • Highest abstraction. Provide light weight API for the developer to work with multithread.
  • GCD takes care of all thread creation, management and scheduling.
  • It also helps to keep track of total number of threads in your app
  • It’ll not cause leak.
  • Two queues are available out of the box: main and global.
  • Can create more queues (using DispatchQueue).
  • Can request exclusive access (using dispatch_barrier_sync and dispatch_barrier_async).
  • limit on maximum thread pool size it is ~66(the maximum GCD thread pool size) + the main thread + some other random non-GCD thread.

Disadvantages of GCD:

  1. you can’t cancel an operation if it is in the queue or it can’t be stopped when it is running
  2. You can’t suspend operation which they are in Queue
  3. You can’t find how many pending operations are there in queue.
  4. You can’t define the maximum number of concurrent operations.

Note:

Enjoy your coding. I hope you learnt something from this blog. Please hit the clap button below 👏 to help others find it!. follow me on Medium.

Geek Culture

Proud to geek out. Follow to join our 1M monthly readers.