Functional Concurrency Primitives

Overview

Build Status

Concurrent

Concurrent is a collection of functional concurrency primitives inspired by Concurrent ML and Concurrent Haskell. Traditional approaches to concurrency like locks, latches, and semaphores all fall under the same category of basic resource protection. While this affords them a large measure of simplicity, their use is entirely ad-hoc, and failing to properly lock or unlock critical sections can lead a program to beachball or worse. In addition, though we have become accustomed to performing work on background threads, communication between these threads is frought with peril.

The primitives in this library instead focus on merging data with protection, choosing to abstract away the use of locks entirely. By approaching concurrency from the data side, rather than the code side, thread-safety, synchronization, and protection become inherent in types rather than in code.

Take this simple example:

import struct Concurrent.Chan

/// A Channel is an unbounded FIFO stream of values with special semantics
/// for reads and writes.
let chan = Chan<Int>()

/// All writes to the Channel always succeed.  The Channel now contains `1`.
chan.write(1) // happens immediately

/// Reads to non-empty Channels occur immediately.  The Channel is now empty.
let x1 = chan.read()

/// But if we read from an empty Channel the read blocks until we write to the Channel again.
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
  chan.write(2) // Causes the read to suceed and unblocks the reading thread.
}

let x2 = chan.read() // Blocks until the dispatch block is executed and the Channel becomes non-empty.

Unlike lock-based protection mechanisms, we can wrap mutable variables that must be accessed concurrently in an MVar.

import class Concurrent.MVar

/// An MVar (Mutable Variable) is a thread-safe synchronizing variable that can be used for
/// communication between threads.
///
/// This MVar is currently empty.  Any reads or writes to it will block until it becomes "full".
let counter : MVar<Int> = MVar()

/// Attempt to increment the counter from 3 different threads.  Because the counter is empty,
/// all of these writes will block until a value is put into the MVar.
DispatchQueue.global().async {
  counter.modify_ { $0 + 1 }
  print("Modifier #1")
}
DispatchQueue.global().async {
  counter.modify_ { $0 + 1 }
  print("Modifier #2")
}
DispatchQueue.global().async {
  counter.modify_ { $0 + 1 }
  print("Modifier #3")
}

/// All the writes will now proceed and unblock each thread in turn.  The order of writes
/// is determined by the order in which each thread called `modify(_ :)`.
counter.put(0)

// > "Modifier #1"
// > "Modifier #3"
// > "Modifier #2"

/// Empties the MVar.  If we just wanted the value without emptying it, we would use
/// `read()` instead.
///
/// Because our take occured after the put, all of the modifications we made before will
/// complete before we read the final value.
print(counter.take()) // 3

MVars can also be used purely as a synchronization point between multiple threads:

import class Concurrent.MVar

let pingvar : MVar<String> = MVar()
let pongvar : MVar<String> = MVar()
let done = MVar<()>() // The synchronization point

/// Puts a value into the now-empty ping variable then blocks waiting for the
/// pong variable to have a value put into it.  Once we have read the pong variable,
/// we unblock the done MVar, and in doing so, unblock the main thread.
DispatchQueue.global().async {
  pingvar.put("ping")
  _ = pongvar.take()
  done.put(())
}

/// Takes the contents of the ping variable then puts a value into the pong variable
/// to unblock the take we just performed.
DispatchQueue.global().async {
  _ = pingvar.take()
  pongvar.put("pong")
}

/// Blocks until all work has completed.
done.take()

Concurrent also exposes a structure for Software Transactional Memory for safe and structured access to shared memory:

typealias Account = TVar<UInt>

/// Some atomic operations
func withdraw(from account : Account, amount : UInt) -> STM<()> { 
  return account.read().flatMap { balance in
    if balance > amount {
      return account.write(balance - amount)
    }
    throw TransactionError.insufficientFunds
  } 
}

func deposit(into account : Account, amount : UInt) -> STM<()> { 
  return account.read().flatMap { balance in
    return account.write(balance + amount)
  }
}

func transfer(from : Account, to : Account, amount : UInt) -> STM<()> { 
  return from.read().flatMap { fromBalance in
    if fromBalance > amount {
      return withdraw(from: from, amount: amount)
          .then(deposit(into: to, amount: amount))
    }
    throw TransactionError.insufficientFunds
  }
}

/// Here are some bank accounts represented as TVars - transactional memory
/// variables.
let alice = Account(200)
let bob = Account(100)

/// All account activity that will be applied in one contiguous transaction.
/// Either all of the effects of this transaction apply to the accounts or
/// everything is completely rolled back and it was as if nothing ever happened.
let finalStatement = 
    transfer(from: alice, to: bob, amount: 100)
        .then(transfer(from: bob, to: alice, amount: 20))
        .then(deposit(into: bob, amount: 1000))
        .then(transfer(from: bob, to: alice, amount: 500))
        .atomically()

System Requirements

Concurrent supports OS X 10.9+ and iOS 7.0+.

Installation

Swift Package Manager

  • Add Concurrent to your Package.swift file's dependencies section:
.package(url: "https://github.com/typelift/Concurrent.git", "0.4.0"..<"1.0.0")

Carthage

Create a Cartfile that lists the framework and run carthage bootstrap. Follow the instructions to add $(SRCROOT)/Carthage/Build/iOS/Concurrent.framework to an iOS project.

github "typelift/Concurrent"

Manually

  1. Download and drop /Sources folder in your project.
  2. Congratulations!

Framework

  • Drag Concurrent.xcodeproj or Concurrent-iOS.xcodeproj into your project tree as a subproject
  • Under your project's Build Phases, expand Target Dependencies
  • Click the + and add Concurrent
  • Expand the Link Binary With Libraries phase
  • Click the + and add Concurrent
  • Click the + at the top left corner to add a Copy Files build phase
  • Set the directory to Frameworks
  • Click the + and add Concurrent

License

Concurrent is released under the MIT license.

Comments
  • Installation section

    Installation section

    Hey @typelift, your library is really interesting.

    The only problem I found was the README.md, which is a little bit confusing. I created this iOS Open source Readme Template so you can take a look on how to better organize. If you want, I can help you to organize the lib.

    What are your thoughts?

    opened by lfarah 3
  • Fixed hang when compiled with Release configuration

    Fixed hang when compiled with Release configuration

    Assertion conditions are @autoclosures which aren't executed when compiled in Release mode. This led to an infinite hang during execution of the atomically() method.

    opened by ypogribnyi 1
  • Fix bugs in the STM

    Fix bugs in the STM

    Previously the upcast nonsense would change the identity of the TVar in the middle of the computation so as long as you had the transaction log for a computation it would do the right thing, but the second you tried to interact imperatively the TVars would lose their stored values - which kinda defeats the point of them being variables. This redoes the logic to make a series of - what I hope are - safe assumptions about type layout.

    We're still type safe, we're just checking a lot of it by hand now.

    opened by CodaFi 1
  • Rewrite the STM

    Rewrite the STM

    This implementation leaves much to be desired, but it seems to work a whole helluva lot better than the last STM even if it isn't using as much type-level nonsense. There is some unfortunate trickery needed to get the witnesses for the transaction log entries to line up correctly, but given that the entries really only exist to act as keys in a hash table it shouldn't be a problem. Tests are GHC's as usual.

    opened by CodaFi 1
  • Using transactions from multiple threads

    Using transactions from multiple threads

    Hi, I am trying to use the STM system to run simultaneous transactions from multiple threads. Unfortunately it seems either my usage is incorrect, or my understanding of the API is. Can you please help me out ?

    Code :

    for _ in 1...3 {
        Thread {
    	let _ =
    		transfer(from: alice, to: bob, amount: 100)
    		.then(transfer(from: bob, to: alice, amount: 20))
    		.then(deposit(into: bob, amount: 1000))
    		.then(transfer(from: bob, to: alice, amount: 500))
    		.atomically()
        }.start()
    }
    

    Result : If fails on this pre-condition

    precondition(transactionEnterSucceed, "Transaction already running on current thread")

    opened by rhishikeshj 1
  • Swap is not atomic, is this by design?

    Swap is not atomic, is this by design?

    /// Atomically, take a value from the `MVar`, put a given new value in the
    /// `MVar`, then return the `MVar`'s old value.
    public func swap(_ x : A) -> A {
    	let old = self.take()
    	self.put(x)
    	return old
    }
    

    The thread using swap might context-switch right after the take completes before the put -- if another thread puts then, this put will block.

    opened by bkase 2
Releases(0.8.0)
  • 0.8.0(Mar 29, 2019)

  • 0.7.0(Apr 12, 2018)

  • 0.6.0(Sep 23, 2017)

    ⚠️ Breaking Changes Ahead ⚠️

    Concurrent now supports Xcode 9 and is being built in Swift 4.0. While no change to the API were made, this does mean we no longer support the Xcode 8 toolchain.

    Source code(tar.gz)
    Source code(zip)
  • 0.5.0(Jun 4, 2017)

  • 0.4.0(Sep 18, 2016)

    ⚠️ Breaking Changes Ahead ⚠️

    Concurrent now supports Swift 3, Xcode 8, and the Swift Package Manager. We've also removed the ability to interact with pthreads as first class citizens because most valid use cases are subsumed by libDispatch.

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Sep 3, 2016)

    Concurrent now builds for Swift 2.3

    STM is back!

    After a long hiatus for maintenance, Concurrent now supports Software Transactional Memory in the form of STM as well as a whole host of data structures that use it to guarantee deterministic access and modifications to shared memory:

    • TVar - Shared memory that uses STM as a mediator for atomic operations
    • TMVar - A hybrid of a TVar and an MVar for transactional shared mutable references
    • TChan - A transactional channel
    • TQueue - A transactional implementation of TChan with a queue interface
    • TBQueue - A transactional implementation of bounded queues
    • TSem - A transactional implementation of a counting semaphore
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Sep 12, 2015)

    Concurrent now fully supports Swift 2.0!

    • Dependence on Swiftz has been broken. This library now stands independently.
    • Exception handling has been removed. Please use Swift's exception mechanisms.
    • Box<T> has been removed from all internal and external APIs.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Jun 7, 2015)

Owner
TypeLift
Libraries to simplify development of Swift programs by utilising the type system.
TypeLift
Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency.

AsyncObjects Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency. Overview While Swif

SwiftyLab 20 Jan 3, 2023
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
A Swift DSL that allows concise and effective concurrency manipulation

NOTE Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves man

Jason Fieldman 25 May 24, 2019
⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Said Sikira 814 Oct 30, 2022
Swift concurrency collection support

AsyncCollections Functions for running async processes on Swift Collections ForEach Run an async function on every element of a Sequence. await array.

Adam Fowler 11 Jul 11, 2022
An introduction to using Swift's new concurrency features in SwiftUI

SwiftUI Concurrency Essentials An introduction to using Swift's new concurrency features in SwiftUI Discuss with me · Report Bug · Request Feature Art

Peter Friese 80 Dec 14, 2022
AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency

AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency, such as combineLatest and distinctUntilChanged.

Ben Pious 3 Jan 18, 2022
The projects and materials that accompany the Modern Concurrency in Swift book

Modern Concurrency in Swift: Materials This repo contains all the downloadable materials and projects associated with the Modern Concurrency in Swift

raywenderlich 137 Dec 16, 2022
Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6.

ConcurrencyCompatibility Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6. Xcode 13.2 adds backwards deployme

Zachary Waldowski 9 Jan 3, 2023
Type-safe networking with Swift Concurrency

AsyncRequest AsyncRequest is a type-safe framework for building a suite of requests to communicate with an API, built on top of Swift Concurrency. Ins

Light Year Software, LLC 1 Feb 9, 2022
A declarative state management and dependency injection library for SwiftUI x Concurrency

A declarative state management and dependency injection library for SwiftUI x Concurrency

Ryo Aoyama 199 Jan 1, 2023
Slack message generator and API client, written in Swift with Result Builders and Concurrency

Slack Message Client This package provides a Swift object model for a Slack Block Kit message, as well as a Result Builder convenience interface for e

Mike Lewis 2 Jul 30, 2022
A Modern Concurrency and Synchronization for Swift.

##Features Simple Atomic<T> class for numbers and strings. Uncomplicated dispatch keyword for firing off background routines. Awesome Chan<T> for conc

Josh Baker 421 Jun 30, 2022
A complete set of primitives for concurrency and reactive programming on Swift

A complete set of primitives for concurrency and reactive programming on Swift 1.4.0 is the latest and greatest, but only for Swift 4.2 and 5.0 use 1.

AsyncNinja 156 Aug 31, 2022
Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency.

AsyncObjects Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency. Overview While Swif

SwiftyLab 20 Jan 3, 2023
Metron is a comprehensive collection of geometric functions and types that extend the 2D geometric primitives

Metron Geometry, simplified. Metron is a comprehensive collection of geometric functions and types that extend the 2D geometric primitives provided by

Toine Heuvelmans 1k Dec 5, 2022
Keep It Functional - An iOS Functional Testing Framework

IMPORTANT! Even though KIF is used to test your UI, you need to add it to your Unit Test target, not your UI Test target. The magic of KIF is that it

KIF Framework 6.2k Dec 29, 2022
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
A Swift DSL that allows concise and effective concurrency manipulation

NOTE Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves man

Jason Fieldman 25 May 24, 2019
⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Said Sikira 814 Oct 30, 2022