I'm running into an issue when I attempt to cache a value for as long as it's valid and update it when it becomes invalid. I believe the issue is due to my attempt to share state across async executions. Further, this component lives in a multi-threaded / concurrent environment.
The error I'm seeing that I don't know how to fix is
future is not `Send` as this value is used across an await
Following is a minimum example that I could come up with (it also features some ownership issues) that generally captures my use-case and the issue I'm seeing. Here is a playground of the code.
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::{Arc, Mutex};
struct Creds {
expires_at: DateTime<Utc>,
}
impl Creds {
fn is_expired(&self) -> bool {
self.expires_at.le(&Utc::now())
}
}
#[async_trait]
trait CredsProvider {
async fn get_creds(&self) -> Creds;
}
struct MyCredsProvider {
cached_creds: Arc<Mutex<Option<Creds>>>,
}
impl MyCredsProvider {
fn new() -> Self {
MyCredsProvider {
cached_creds: Arc::new(Mutex::new(None)),
}
}
async fn inner_get_creds(&self) -> Creds {
todo!()
}
}
#[async_trait]
impl CredsProvider for MyCredsProvider {
async fn get_creds(&self) -> Creds {
let mg = self
.cached_creds
.lock()
.expect("Unable to get lock on creds mutex");
if mg.is_some() && !mg.as_ref().unwrap().is_expired() {
return mg.unwrap();
}
let new_creds = self.inner_get_creds().await;
*mg = Some(new_creds);
return new_creds;
}
}
#[tokio::main]
async fn main() {
MyCredsProvider::new();
// Some multi-threaded / concurrent logic to periodically refresh creds
todo!()
}
I wasn't sure how to include this in the example but in main imagine multiple worker threads running concurrently / parallel that each call CredsProvider.get_creds and then use these creds to perform some work (if you can add that to a complete working example, that'd be much appreciated for my edification). Assume MyCredsProvider.inner_get_creds is expensive and should only be called when the cached creds expire.
How do I solve this? I thought that the Arc<Mutex<>> would be enough but it seems not. At one point, I tried making Creds and trait so that I could have Arc<Mutex<Option<Box<dyn Creds + Send + Sync>>>> but that felt like the wrong path and didn't work.
Thanks.
You may would like to switch to tokio::sync::Mutex (playground).
It solves
future is not `Send` as this value is used across an await
Code:
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
struct Creds {
expires_at: DateTime<Utc>,
}
impl Creds {
fn is_expired(&self) -> bool {
self.expires_at.le(&Utc::now())
}
}
#[async_trait]
trait CredsProvider {
async fn get_creds(&self) -> Creds;
}
struct MyCredsProvider {
cached_creds: Arc<Mutex<Option<Creds>>>,
}
impl MyCredsProvider {
fn new() -> Self {
MyCredsProvider {
cached_creds: Arc::new(Mutex::new(None)),
}
}
async fn inner_get_creds(&self) -> Creds {
todo!()
}
}
#[async_trait]
impl CredsProvider for MyCredsProvider {
async fn get_creds(&self) -> Creds {
let mut mg = self
.cached_creds
.lock()
.await;
if mg.is_some() && !mg.as_ref().unwrap().is_expired() {
return mg.clone().unwrap();
} else {
let new_creds = self.inner_get_creds().await;
*mg = Some(new_creds.clone());
return new_creds;
}
}
}
#[tokio::main]
async fn main() {
MyCredsProvider::new();
// Some multi-threaded / concurrent logic to periodically refresh creds
todo!()
}
I have a function as follows
pub fn registration(student_id: &T::StudentId, registrar: &T::RegistrarID) {
// More code here.
if num_of_students < student_limit {
Self::function_one(®istrar, &num_of_students);
} else {
Self::function_two(&num_of_students);
}
}
In unit tests, I am planning to check whether function_one or function_two was called.
#[test]
fn registration_more_students_should_call_functon_one() {
with_test_data(
&mut TestBuilder::default().num_of_students(1000).build(),
|| {
//assert_called!(module_name::registration("TV:009", "DF-000-09"));
},
);
}
How can I test if a function was called in Rust?
Strong opinion alert: you are doing your testing wrong. This is on the same level as "how do I test a private method". You shouldn't care about the implementation of registration to this level of detail.
That being said, if it's actually important to know which if branch is taken, then use dependency injection:
fn registration(mut registration: impl Registration, registrar: i32) {
let num_of_students = 0;
let student_limit = 0;
if num_of_students < student_limit {
registration.function_one(registrar, num_of_students);
} else {
registration.function_two(num_of_students);
}
}
trait Registration {
fn function_one(&mut self, registrar: i32, num_of_students: i32);
fn function_two(&mut self, num_of_students: i32);
}
impl<R: Registration> Registration for &'_ mut R {
fn function_one(&mut self, registrar: i32, num_of_students: i32) {
(**self).function_one(registrar, num_of_students)
}
fn function_two(&mut self, num_of_students: i32) {
(**self).function_two(num_of_students)
}
}
/*
// An example implementation for production
struct DatabaseRegistration;
impl Registration for DatabaseRegistration {
fn function_one(&mut self, registrar: i32, num_of_students: i32) {
eprintln!("Do DB work: {}, {}", registrar, num_of_students)
}
fn function_two(&mut self, num_of_students: i32) {
eprintln!("Do DB work: {}", num_of_students)
}
}
*/
#[cfg(test)]
mod test {
use super::*;
#[derive(Debug, Copy, Clone, Default)]
struct TestRegistration {
calls_to_one: usize,
calls_to_two: usize,
}
impl Registration for TestRegistration {
fn function_one(&mut self, _: i32, _: i32) {
self.calls_to_one += 1;
}
fn function_two(&mut self, _: i32) {
self.calls_to_two += 1;
}
}
#[test]
fn calls_the_right_one() {
let mut reg = TestRegistration::default();
registration(&mut reg, 42);
assert_eq!(1, reg.calls_to_two)
}
}
Once you have done this, then you can see that registration calls the appropriate trait function (as shown in the example test).
This prevents your production code from having test-specific detritus strewn about while also giving you the ability to be more flexible and test more cases rapidly.
See also:
How can I test stdin and stdout?
How to mock external dependencies in tests?
Is there a cleaner way to test functions that use functions that require user input in Rust?
How can I test Rust methods that depend on environment variables?
Is there a way of detecting whether code is being called from tests in Rust?
Here is a naïve attempt using #[cfg(test)] in multiple places:
struct Registration {
students: Vec<String>,
#[cfg(test)]
function_1_called: bool,
}
impl Registration {
fn new() -> Self {
Registration {
students: Vec::new(),
#[cfg(test)]
function_1_called: false,
}
}
fn function_1(&mut self, students: Vec<String>) {
self.students.extend(students);
#[cfg(test)]
{
self.function_1_called = true;
}
}
fn function_2(&mut self, students: Vec<String>) {}
fn f(&mut self, students: Vec<String>) {
if students.len() < 100 {
self.function_1(students);
} else {
self.function_2(students);
}
}
}
fn main() {
println!("Hello, world!");
let r = Registration::new();
// won't compile during `run`:
// println!("{}", r.function_1_called);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_f() {
let mut r = Registration::new();
r.function_1(vec!["Alice".to_string(), "Bob".to_string()]);
assert!(r.function_1_called);
}
}
The code is loosely based on the snippets that you provided. There is a Registration struct that holds a list of student names, two methods function_1 and function_2 for registering students, and a function f that chooses between function_1 and function_2 depending o how many students there are.
During tests, Registration is compiled with an additional Boolean variable function_1_called. This variable is set only if function_1 is called (the block of code that does that is also marked with #[cfg(test)]).
In combination, this makes an additional Boolean flag available during the tests, so that you can make assertions like the following one:
assert!(r.function_1_called);
Obviously, this could work for structures much more complicated than a single boolean flag (which does not at all mean that you should actually do it).
I cannot comment on whether this is idiomatic in Rust or not. The whole setup feels as if it should be hidden behind fancy macros, so if this style of testing is used in Rust at all, there should already be crates that provide those (or similar) macros.
I'm experiencing weird behavior with cpu pools:
#[macro_use]
extern crate lazy_static;
extern crate tokio_core;
extern crate futures;
extern crate futures_cpupool;
use std::time::Duration;
use futures_cpupool::{CpuPool, Builder, CpuFuture};
use futures::Stream;
use futures::{Future, future, lazy};
use futures::sync::mpsc;
use futures::Sink;
lazy_static! {
static ref CPU_POOL: CpuPool = {
Builder::new()
.pool_size(10)
.after_start(|| {
println!("Pool started one thread");
})
.before_stop(|| {
println!("Pool stopped one thread");
})
.create()
};
}
struct Producer {}
impl Producer {
fn search_names(&self) -> Box<Stream<Item = String, Error = String> + Send> {
let (mut tx, rx) = mpsc::channel::<Result<String, String>>(1);
println!("Creating producer thread...");
let producer_cpu: CpuFuture<(), ()> = CPU_POOL.spawn(lazy(move || {
println!(" -- Begin to produce names");
for i in 0..10 {
match tx.send(Ok("name".to_string())).wait() {
Ok(t) => {
println!(" -- sent the name");
tx = t
}
Err(err) => {
println!(" -- Error occured sending name! {:?}", err);
break;
}
}
std::thread::sleep(Duration::from_secs(1));
}
future::ok::<(), ()>(())
})
.then(|result| {
match result {
Ok(data) => println!("Producer finished with data: {:?}", data),
Err(err) => println!("Producer finished with error: {:?}", err),
}
future::ok::<(), ()>(())
}));
rx.then(|r| r.unwrap()).boxed()
}
}
fn main() {
let producer = Producer {};
let names = CPU_POOL.spawn(producer.search_names()
.map(|name| {
println!("name = {:?}", name);
name
})
.collect()
.then(|result| {
match result {
Ok(data) => println!("Finished to read producer {:?}", data),
Err(err) => println!("Error reading stream of producer! {:?}", err),
}
future::ok::<(), ()>(())
}));
names.wait();
}
Here is the corresponding Cargo.toml
[package]
name = "example"
version = "0.1.0"
[dependencies]
lazy_static = "^0.1.*"
tokio-core = "^0.1"
futures = "^0.1"
futures-cpupool = "^0.1"
I'm running on Rust nightly (1.16.0-nightly (df8debf6d 2017-01-25))
I would expect this program to generate the 10 Strings, output it via println and exit. However, most of the time, the program doesn't generate the Strings and exits normally, other times the Strings are correctly generated.
Here is the output of the first case:
Creating producer thread...
Pool started one thread
Finished to read producer []
Pool started one thread
Pool started one thread
Pool started one thread
Pool started one thread
And the output when Strings get generated
Pool started one thread
Pool started one thread
Pool started one thread
Pool started one thread
Creating producer thread...
-- Begin to produce names
-- sent the name
name = "name"
Pool started one thread
-- sent the name
name = "name"
Producer finished with data: ()
Finished to read producer ["name", "name"]
I got the feeling that, for the first case, the producer thread doesn't get scheduled on the thread pool for whatever reason. I must be missing something but I don't know what.
The cause of the problem is the early drop of the producer future.
On the method search_names, the CpuFuture that produces value is dropped when search_names returns. When dropped, a CpuFuture is cancelled, thus skipping production of the values.
The difference in behavior certainly comes from a race between the drop of the future and it's execution.
A solution is to reference the producer future all along the application like this:
#[macro_use]
extern crate lazy_static;
extern crate tokio_core;
extern crate futures;
extern crate futures_cpupool;
use std::time::Duration;
use futures_cpupool::{CpuPool, Builder, CpuFuture};
use futures::Stream;
use futures::{Future, future, lazy};
use futures::sync::mpsc;
use futures::Sink;
lazy_static! {
static ref CPU_POOL: CpuPool = {
Builder::new()
.pool_size(5)
.after_start(|| {
println!("Pool started one thread");
})
.before_stop(|| {
println!("Pool stopped one thread");
})
.create()
};
}
struct Producer {}
impl Producer {
fn search_names(&self) -> (CpuFuture<(), ()>, Box<Stream<Item = String, Error = String> + Send>) {
let (mut tx, rx) = mpsc::channel::<Result<String, String>>(1);
println!("Creating producer thread...");
let producer_cpu: CpuFuture<(), ()> = CPU_POOL.spawn(
lazy(move || {
println!(" -- Begin to produce names");
for i in 0..2 {
match tx.send(Ok("name".to_string())).wait() {
Ok(t) => {
println!(" -- sent the name");
tx = t
},
Err(err) => {
println!(" -- Error occured sending name! {:?}", err);
break
},
}
std::thread::sleep(Duration::from_secs(1));
}
future::ok::<(), ()>(())
}).then(|result| {
match result {
Ok(data) => println!("Producer finished with data: {:?}", data),
Err(err) => println!("Producer finished with error: {:?}", err),
}
future::ok::<(), ()>(())
})
);
(
producer_cpu,
rx.then(|r| r.unwrap()).boxed()
)
}
}
fn main() {
let producer = Producer {};
let (future, stream) = producer.search_names();
let names = CPU_POOL.spawn(
stream
.map(|name| {
println!("name = {:?}", name);
name
})
.collect()
.then(|result| {
match result {
Ok(data) => println!("Finished to read producer {:?}", data),
Err(err) => println!("Error reading stream of producer! {:?}", err)
}
future::ok::<(), ()>(())
})
);
names.wait();
}
I've searched the Swift book, but can't find the Swift version of #synchronized. How do I do mutual exclusion in Swift?
You can use GCD. It is a little more verbose than #synchronized, but works as a replacement:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
I was looking for this myself and came to the conclusion there's no native construct inside of swift for this yet.
I did make up this small helper function based on some of the code I've seen from Matt Bridges and others.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Usage is pretty straight forward
synced(self) {
println("This is a synchronized closure")
}
There is one problem I've found with this. Passing in an array as the lock argument seems to cause a very obtuse compiler error at this point. Otherwise though it seems to work as desired.
Bitcast requires both operands to be pointer or neither
%26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!
I like and use many of the answers here, so I'd choose whichever works best for you. That said, the method I prefer when I need something like objective-c's #synchronized uses the defer statement introduced in swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
The nice thing about this method, is that your critical section can exit the containing block in any fashion desired (e.g., return, break, continue, throw), and "the statements within the defer statement are executed no matter how program control is transferred."1
You can sandwich statements between objc_sync_enter(obj: AnyObject?) and objc_sync_exit(obj: AnyObject?). The #synchronized keyword is using those methods under the covers. i.e.
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
Analog of the #synchronized directive from Objective-C can have an arbitrary return type and nice rethrows behaviour in Swift.
// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}
The use of the defer statement lets directly return a value without introducing a temporary variable.
In Swift 2 add the #noescape attribute to the closure to allow more optimisations:
// Swift 2
func synchronized<T>(lock: AnyObject, #noescape _ body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}
Based on the answers from GNewc [1] (where I like arbitrary return type) and Tod Cunningham [2] (where I like defer).
SWIFT 4
In Swift 4 you can use GCDs dispatch queues to lock resources.
class MyObject {
private var internalState: Int = 0
private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newState) {
internalQueue.sync { internalState = newState }
}
}
}
In modern Swift 5, with return capability:
/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
#discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
Use it like this, to take advantage the return value capability:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Or like that otherwise:
synchronized(self) {
// Your code here
yourCode()
}
To add return functionalty, you could do this:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Subsequently, you can call it using:
func importantMethod(...) -> Bool {
return synchronize(self) {
if(feelLikeReturningTrue) { return true }
// do other things
if(feelLikeReturningTrueNow) { return true }
// more things
return whatIFeelLike ? true : false
}
}
Using Bryan McLemore answer, I extended it to support objects that throw in a safe manor with the Swift 2.0 defer ability.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
In the "Understanding Crashes and Crash Logs" session 414 of the 2018 WWDC they show the following way using DispatchQueues with sync.
In swift 4 should be something like the following:
class ImageCache {
private let queue = DispatchQueue(label: "sync queue")
private var storage: [String: UIImage] = [:]
public subscript(key: String) -> UIImage? {
get {
return queue.sync {
return storage[key]
}
}
set {
queue.sync {
storage[key] = newValue
}
}
}
}
Anyway you can also make reads faster using concurrent queues with barriers. Sync and async reads are performed concurrently and writing a new value waits for previous operations to finish.
class ImageCache {
private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
private var storage: [String: UIImage] = [:]
func get(_ key: String) -> UIImage? {
return queue.sync { [weak self] in
guard let self = self else { return nil }
return self.storage[key]
}
}
func set(_ image: UIImage, for key: String) {
queue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.storage[key] = image
}
}
}
Try: NSRecursiveLock
A lock that may be acquired multiple times by the same thread without
causing a deadlock.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
The Objective-C synchronization feature supports recursive and
reentrant code. A thread can use a single semaphore several times in a
recursive manner; other threads are blocked from using it until the
thread releases all the locks obtained with it; that is, every
#synchronized() block is exited normally or through an exception.
Source
Swift 3
This code has the re-entry ability and can work with Asynchronous function calls. In this code, after someAsyncFunc() is called, another function closure on the serial queue will process but be blocked by semaphore.wait() until signal() is called. internalQueue.sync shouldn't be used as it will block the main thread if I'm not mistaken.
let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)
internalQueue.async {
self.semaphore.wait()
// Critical section
someAsyncFunc() {
// Do some work here
self.semaphore.signal()
}
}
objc_sync_enter/objc_sync_exit isn't a good idea without error handling.
Use NSLock in Swift4:
let lock = NSLock()
lock.lock()
if isRunning == true {
print("Service IS running ==> please wait")
return
} else {
print("Service not running")
}
isRunning = true
lock.unlock()
Warning
The NSLock class uses POSIX threads to implement its locking behavior. When sending an unlock message to an NSLock object, you must be sure that message is sent from the same thread that sent the initial lock message. Unlocking a lock from a different thread can result in undefined behavior.
With Swift's property wrappers, this is what I'm using now:
#propertyWrapper public struct NCCSerialized<Wrapped> {
private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")
private var _wrappedValue: Wrapped
public var wrappedValue: Wrapped {
get { queue.sync { _wrappedValue } }
set { queue.sync { _wrappedValue = newValue } }
}
public init(wrappedValue: Wrapped) {
self._wrappedValue = wrappedValue
}
}
Then you can just do:
#NCCSerialized var foo: Int = 10
or
#NCCSerialized var myData: [SomeStruct] = []
Then access the variable as you normally would.
With the advent of Swift concurrency, we would use actors.
You can use tasks to break up your program into isolated, concurrent
pieces. Tasks are isolated from each other, which is what makes it
safe for them to run at the same time, but sometimes you need to share
some information between tasks. Actors let you safely share
information between concurrent code.
Like classes, actors are reference types, so the comparison of value
types and reference types in Classes Are Reference Types applies to
actors as well as classes. Unlike classes, actors allow only one task
to access their mutable state at a time, which makes it safe for code
in multiple tasks to interact with the same instance of an actor. For
example, here’s an actor that records temperatures:
actor TemperatureLogger {
let label: String
var measurements: [Int]
private(set) var max: Int
init(label: String, measurement: Int) {
self.label = label
self.measurements = [measurement]
self.max = measurement
}
}
You introduce an actor with the actor keyword, followed by its definition in a pair of braces. The TemperatureLogger actor has properties that other code outside the actor can access, and restricts the max property so only code inside the actor can update the maximum value.
For more information, see WWDC video Protect mutable state with Swift actors.
For the sake of completeness, the historical alternatives include:
GCD serial queue: This is a simple pre-concurrency approach to ensure that one one thread at a time will interact with the shared resource.
Reader-writer pattern with concurrent GCD queue: In reader-writer patterns, one uses a concurrent dispatch queue to perform synchronous, but concurrent, reads (but concurrent with other reads only, not writes) but perform writes asynchronously with a barrier (forcing writes to not be performed concurrently with anything else on that queue). This can offer a performance improvement over a simple GCD serial solution, but in practice, the advantage is modest and comes at the cost of additional complexity (e.g., you have to be careful about thread-explosion scenarios). IMHO, I tend to avoid this pattern, either sticking with the simplicity of the serial queue pattern, or, when the performance difference is critical, using a completely different pattern.
Locks: In my Swift tests, lock-based synchronization tends to be substantially faster than either of the GCD approaches. Locks come in a few flavors:
NSLock is a nice, relatively efficient lock mechanism.
In those cases where performance is of paramount concern, I use “unfair locks”, but you must be careful when using them from Swift (see https://stackoverflow.com/a/66525671/1271826).
For the sake of completeness, there is also the recursive lock. IMHO, I would favor simple NSLock over NSRecursiveLock. Recursive locks are subject to abuse and often indicate code smell.
You might see references to “spin locks”. Many years ago, they used to be employed where performance was of paramount concern, but they are now deprecated in favor of unfair locks.
Technically, one can use semaphores for synchronization, but it tends to be the slowest of all the alternatives.
I outline a few my benchmark results here.
In short, nowadays I use actors for contemporary codebases, GCD serial queues for simple scenarios non-async-await code, and locks in those rare cases where performance is essential.
And, needless to say, we often try to reduce the number of synchronizations altogether. If we can, we often use value types, where each thread gets its own copy. And where synchronization cannot be avoided, we try to minimize the number of those synchronizations where possible.
Figure I'll post my Swift 5 implementation, built off of the prior answers. Thanks guys! I found it helpful to have one that returns a value too, so I have two methods.
Here is a simple class to make first:
import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
closure()
}
public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
}
Then use it like so if needing a return value:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Or:
Sync.synced(self, closure: {
// do some work synchronously
})
You can create propertyWrapper Synchronised
Here example with NSLock underhood. You can use for synchronisation whatever you want GCD, posix_locks e.t.c
#propertyWrapper public struct Synchronised<T> {
private let lock = NSLock()
private var _wrappedValue: T
public var wrappedValue: T {
get {
lock.lock()
defer {
lock.unlock()
}
return _wrappedValue
}
set {
lock.lock()
defer {
lock.unlock()
}
_wrappedValue = newValue
}
}
public init(wrappedValue: T) {
self._wrappedValue = wrappedValue
}
}
#Synchronised var example: String = "testing"
based on #drewster answer
In conclusion, Here give more common way that include return value or void, and throw
import Foundation
extension NSObject {
func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T
{
objc_sync_enter(lockObj)
defer {
objc_sync_exit(lockObj)
}
return try closure()
}
}
Details
Xcode 8.3.1, Swift 3.1
Task
Read write value from different threads (async).
Code
class AsyncObject<T>:CustomStringConvertible {
private var _value: T
public private(set) var dispatchQueueName: String
let dispatchQueue: DispatchQueue
init (value: T, dispatchQueueName: String) {
_value = value
self.dispatchQueueName = dispatchQueueName
dispatchQueue = DispatchQueue(label: dispatchQueueName)
}
func setValue(with closure: #escaping (_ currentValue: T)->(T) ) {
dispatchQueue.sync { [weak self] in
if let _self = self {
_self._value = closure(_self._value)
}
}
}
func getValue(with closure: #escaping (_ currentValue: T)->() ) {
dispatchQueue.sync { [weak self] in
if let _self = self {
closure(_self._value)
}
}
}
var value: T {
get {
return dispatchQueue.sync { _value }
}
set (newValue) {
dispatchQueue.sync { _value = newValue }
}
}
var description: String {
return "\(_value)"
}
}
Usage
print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)
print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
let newValue = current*2
print("previous: \(current), new: \(newValue)")
return newValue
}
Full Sample
extension DispatchGroup
extension DispatchGroup {
class func loop(repeatNumber: Int, action: #escaping (_ index: Int)->(), completion: #escaping ()->()) {
let group = DispatchGroup()
for index in 0...repeatNumber {
group.enter()
DispatchQueue.global(qos: .utility).async {
action(index)
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
completion()
}
}
}
class ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//sample1()
sample2()
}
func sample1() {
print("=================================================\nsample with variable")
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")
DispatchGroup.loop(repeatNumber: 5, action: { index in
obj.value = index
}) {
print("\(obj.value)")
}
}
func sample2() {
print("\n=================================================\nsample with array")
let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
DispatchGroup.loop(repeatNumber: 15, action: { index in
arr.setValue{ (current) -> ([Int]) in
var array = current
array.append(index*index)
print("index: \(index), value \(array[array.count-1])")
return array
}
}) {
print("\(arr.value)")
}
}
}
What about
final class SpinLock {
private let lock = NSRecursiveLock()
func sync<T>(action: () -> T) -> T {
lock.lock()
defer { lock.unlock() }
return action()
}
}
Why make it difficult and hassle with locks?
Use Dispatch Barriers.
A dispatch barrier creates a synchronization point within a concurrent queue.
While it’s running, no other block on the queue is allowed to run, even if it’s concurrent and other cores are available.
If that sounds like an exclusive (write) lock, it is.
Non-barrier blocks can be thought of as shared (read) locks.
As long as all access to the resource is performed through the queue, barriers provide very cheap synchronization.
dispatch_barrier_async is the better way, while not blocking current thread.
dispatch_barrier_async(accessQueue, {
dictionary[object.ID] = object
})
Based on ɲeuroburɳ, test an sub-class case
class Foo: NSObject {
func test() {
print("1")
objc_sync_enter(self)
defer {
objc_sync_exit(self)
print("3")
}
print("2")
}
}
class Foo2: Foo {
override func test() {
super.test()
print("11")
objc_sync_enter(self)
defer {
print("33")
objc_sync_exit(self)
}
print("22")
}
}
let test = Foo2()
test.test()
Output:
1
2
3
11
22
33
Another method is to create a superclass and then inherit it. This way you can use GCD more directly
class Lockable {
let lockableQ:dispatch_queue_t
init() {
lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
}
func lock(closure: () -> ()) {
dispatch_sync(lockableQ, closure)
}
}
class Foo: Lockable {
func boo() {
lock {
....... do something
}
}