Skip to content

Latest commit

Β 

History

History
381 lines (237 loc) Β· 8.16 KB

ConcurrentProgrammingWithGCDInSwift3.md

File metadata and controls

381 lines (237 loc) Β· 8.16 KB

Concurrency

  • Threads allow execution of code at the same time
  • CPU cores can each execute a single thread at any given time
  • Maintaining code invariants is more difficult with concurrency

Dispatch Queues and Run Loops

λ””μŠ€νŒ¨μΉ˜ 큐λ₯Ό μ΄μš©ν•˜λ©΄ μŠ€μœ„ν”„νŠΈμ˜ ν΄λ‘œμ €λ₯Ό 큐에 등둝할 수 μžˆλ‹€. λ””μŠ€νŒ¨μΉ˜ νλŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•΄ μŠ€λ ˆλ“œλ₯Ό κ°€μ Έμ˜¨λ‹€. 일반적인 μŠ€λ ˆλ“œλŠ” Run Loop을 가지고 있고, Main μŠ€λ ˆλ“œμ˜ κ²½μš°λŠ” Main Run Loopκ³Ό Main 큐λ₯Ό 가지고 μžˆλ‹€.

Asynchronous Execution

λ””μŠ€νŒ¨μΉ˜ 큐에 μ—¬λŸ¬ asynchronous μ•„μ΄ν…œμ΄ λ“±λ‘λ˜μ–΄ μžˆλ‹€λ©΄ μŠ€λ ˆλ“œλ₯Ό κ°€μ Έμ™€μ„œ ν•˜λ‚˜μ”© μ²˜λ¦¬ν•œλ‹€.

Synchronous Execution

synchronous μ•„μ΄ν…œμ„ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„  worker μŠ€λ ˆλ“œμ—μ„œ λ‹€λ₯Έ μŠ€λ ˆλ“œλ‘œ λ„˜μ–΄κ°€μ„œ 처리λ₯Ό ν•œλ‹€. 이후 asynchronous μ•„μ΄ν…œ μ²˜λ¦¬λŠ” λ””μŠ€νŒ¨μΉ˜ 큐의 worker μŠ€λ ˆλ“œμ—μ„œ μ²˜λ¦¬ν•œλ‹€.

Getting Work Off Your Main Thread

  • Create a Dispatch Queue to whitch you submit work
  • Dispatch Queues execute work items in FIFO order
  • Use .async to execute your work on the queue
let queue = DispatchQueue(label: "com.example.imagetransform")

queue.async {
  let smallImage = iamge.resize(to: rect)
}

Getting Back to Your Main Thread

  • Dispatch main queue executes all items on the main thread
  • Simple to chain work between queues
let queue = DispatchQueue(label: "com.example.imagetransform")

queue.async {
  let smallImage = iamge.resize(to: rect)
  
  DispatchQueue.main.async {
    imageView.image = smallImage
  }
}

Controlling Concurrency

  • Thread pool will limit concurrency
  • Worker threads that block can cause more to spawn
  • Choosing the right number of queues to use is important

Structuring Your Application

  • Identify areas of data flow in your application
  • Split into distinct subsystems
  • Queues at subsystem granularity

Chaining vs. Grouping Work

720_1

Grouping Work Together

μ•„μ£Ό κ°„λ‹¨ν•˜κ²Œ DispatchGroup을 λ§Œλ“€ 수 μžˆλ‹€.

let group = DispatchGroup()

queue.async(group: group) { ... }

queue2.async(group: group) { ... }

queue3.async(group: group) { ... }

group.notify(queue: DispatchQueue.main) { ... }

Synchronizing Between Subsystems

  • Can use subsystem serial queues for mutual exclusion
  • Use .sync to safely access properties from subsystems
  • Be aware of "lock ordering" introduced between subsystems
    • 1 -> 2 -> 3 -> 1 이런 μ‹μœΌλ‘œ κ±Έλ©΄ μ•ˆ λœλ‹€! deadlock
var count: Int {
  queue.sync { self.connections.count }
}

Choosing a Quality of Service

  • QoS provides explicit classification of work
  • Indicates developer intent
  • Affects execution properties of your work

QoS list

  • User Interactive
  • User Initiated
  • Utility
  • Background

Using Quality of Service Classes

  • Use .async to submit work with a specific QoS class
  • Dispatch helps resolve priority inversions
  • Create single-purpose queues with a specific QoS class
queue.async(qos: .background) {
  print("Maintenance work")
}

queue.async(qos: .userInitiated) {
  print("Button tapped")
}

DispatchWorkItem

  • By default .async captures execution context at time of submission
  • Create DispatchWorkItem from closures to control execution properties
  • Use .assignCurrentContext to capture current QoS at time of creation
let item = DispatchWorkItem(flags: .assignCurrentContext) {
  print("Hello WWDC 2016!")
}

queue.async(execute: item)

Waiting for Work Items

  • Use .wait on work items to signal that this item needs to execute
  • Dispatch elevates priority of queued work ahead
  • Waiting with a DispatchWorkItem gives ownership information
  • Semaphores and Groups do not admit a concept of ownership

Swift 3 and Synchronization

(Synchronization is not part of the language in Swift 3)

  • Global variables are initially atomically
  • Class properties are not atomic
  • Lazy properties are not initialized atomically

κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μš°λ¦¬λŠ” 동기화λ₯Ό μ–΄λ–»κ²Œ μ‹œν‚¬ 수 μžˆλŠ”μ§€ μ•Œμ•„μ•Ό ν•œλ‹€! 동기화 μ‹œν‚¬ 포인트λ₯Ό 잘λͺ» 작으면 앱에 ν¬λž˜μ‰¬κ°€ λ°œμƒν•˜κ±°λ‚˜ 데이터가 잘λͺ»λ  수 μžˆλ‹€.

μ„Έμ…˜ μΆ”μ²œ > Thread Sanitizer and Static Analysis

Traditional C Locks in Swift

  • The Darwin module exposes traditional C lock types
    • correct use of C struct based locks such as pthread_mutex_t is incredibly hard

Correct Use of Traditional Locks

  • Foundation.Lock can be used safely because it is a class
  • Derive an Objective-C base class with struct based locks as ivars
@implementation LockableObject {
  os_unfair_lock _lock;
}

- (instancetype)init ...;
- (void)lock		{ os_unfair_lock_lock(&_lock); }
- (void)unlock 	{ os_unfair_lock_lock(&_lock); }
@end

Use GCD for Synchronization

이 방법이 lock을 μ‚¬μš©ν•˜λŠ” 방법보닀 더 쒋은 방법!

  • Use DispatchQueue.sync(execute:)
    • harder to misuse than traditional locks, more robust
    • better instrumentation (Xcode, assertions, ...)
class MyObject {
  private let internalState: Int
  private let internalQueue: DispatchQueue
  var state: Int {
    get {
      return internalQueue.sync { internalState }
    }
    set (newState) {
      internalQueue.sync { internalState = newState }
    }
  }
}

이 νŒ¨ν„΄μ€ κ°„λ‹¨ν•˜μ§€λ§Œ λ‹€μ–‘ν•œ 상황에 ν™•μž₯ν•΄μ„œ 적용이 κ°€λŠ₯ν•˜λ‹€.

Preconditions

  • GCD lets you express several preconditions
    • Code is running on a given queue
    • Code is not running on a given queue
dispatchPrecondition(.onQueue(expectedQueue))
dispatchPrecondition(.notOnQueue(unexpectedQueue))

Object Lifecycle in a Concurrent World

  1. Single threaded setup: 객체 생성, 객체 ν”„λ‘œνΌν‹° μ„€μ •
  2. activate the concurrent state machine
  3. invalidate the concurrent state machine
  4. Single threaded deallocation

Setup

class BusyController: SubsystemObserving {
  init(...) { ... }
}

Activation

class BusyController: SubsystemObserving {
  init(...) { ... }
  
  func activate() {
    DataTransform.sharedInstance.register(observer: self, queue: DispatchQueue.main)
  }
}

Activate State Machine

class BusyController: SubsystemObserving {
  func systemStarted(...) { ... }
  func systemDone(...) { ... }
}

Deallocation

class BusyController: SubsystemObserving {
  deinit {
    DataTransform.sharedInstance.unregister(observer: self)
  }
}

Invalidation

class BusyController: SubsystemObserving  {
  private var invalidated: Bool = false
  
  func invalidate() {
    dispatchPrecondition(.onQueue(DispatchQueue.main))
    invalidated = true
    DataTransform.sharedInstance.unregister(observer: self)
  }
  
  func systemStarted(...) {
    if invalidated { return }
  }
  
  deinit {
    precondition(invalidated)
  }
}

GCD Object Lifecycle

Setup

  • Attributes and target queue
  • Source handlers
let q = DispatchQueue(label: "com.example.queue", attributes: [[.autoreleaseWorkItem]])

let source = DispatchSource.read(fileDescriptor: fd, queue: q)

source.setEventHandler { /* handle your event here */ }
source.setCancelHandler { close(fd) }

Activation

  • Properties of dispatch objects must not be mutated after activation
    • Queues can also be created inactive
extension DispatchObject {
  func activate()
}

let queue = DispatchQueue(label: "com.example.queue", attributes: [.initiallyInactive])

Cancellation

  • Sources require explicit cancellation
    • Event monitoring is stopped
    • Cancellation handler runs
    • All handlers are deallocated
extension DispatchSource {
  func cancel()
}

let source = DispatchSource.read(fileDescriptor: fd, queue: q)
source.setCancelHandler { close(Fd) }

Deallocation Hygine

  • GCD Objects expect to be in a defined state at deallocation
    • Activated
    • Not suspended

Summary

  • Organize your application around data flows into independent subsystems
  • Synchronize state with Dispatch Queues
  • Use the active/invalidate pattern