15

I have the following code:

func foo() {
    var sum = 0
    var pendingElements = 10

    for i in 0 ..< 10 {
        proccessElementAsync(i) { value in
            sum += value
            pendingElements--

            if pendingElements == 0 {
                println(sum)
            }
        }
    }
}

In this case the function proccessElementAsync, as its name indicates, process its input parameter asynchronously and when it finishes it calls its corresponding completion handler.

The inconvenience with this approach is that since the variable pendingElements is accessed through multiple threads, then it is possible that the statement if pendingElements == 0 will never has value true.

In C# we are able to do something like:

Object lockObject = new Object();
...

lock (lockObject) {
    pendingElements--;

    if (pendingElements == 0) {
        Console.WriteLine(sum);
    }
}

and this ensures that this variable will be accessed only for a thread at the same time. Is there any way of getting the same behavior in Swift?

Reynaldo Aguilar
  • 1,906
  • 1
  • 15
  • 29
  • 2
    https://www.mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html – Jonathon Reinhart Apr 22 '15 at 16:22
  • Side note: in C# `lock(someIntValue)` does not do anything remotely close to what you trying to achieve... In C# please consider following default guideline of creating special objects only for locking... – Alexei Levenkov Apr 22 '15 at 16:24
  • 1
    In C# we would be able to do `Interlocked.Decrement` – Bas Apr 22 '15 at 16:26
  • @Bas `Interlocked.Decrement` will not help to protect 2 variables - like `pendingElements` and `sum` as shown. – Alexei Levenkov Apr 22 '15 at 16:30
  • 1
    @AlexeiLevenkov I have updated the c# code, I think that I was too informal with it. Sorry for that – Reynaldo Aguilar Apr 22 '15 at 16:32
  • 2
    @ReynaldoAguilar - looks good now. In general keep in mind that it is not possible for readers to distinguish between "informal/quickly written sample" and "complete misunderstanding of a concept". So showing correctly looking code significantly improves chances of answer to actually address what you are looking for. – Alexei Levenkov Apr 22 '15 at 16:38

2 Answers2

20

Hope this will help you.

func lock(obj: AnyObject, blk:() -> ()) {
    objc_sync_enter(obj)
    blk()
    objc_sync_exit(obj)
}

var pendingElements = 10

func foo() {
    var sum = 0
    var pendingElements = 10

    for i in 0 ..< 10 {
        proccessElementAsync(i) { value in

            lock(pendingElements) {
                sum += value
                pendingElements--

                if pendingElements == 0 {
                    println(sum)
                }
            }

        }
    }
}
YongUn Choi
  • 366
  • 2
  • 4
  • Thanks for your reply. If the calls objc_sync_enter(obj) and objc_sync_exit do what I believe, then your answer solves my problem. Can you add to your answer some details about what these functions do? I think that this will improve the quality of the answer – Reynaldo Aguilar Apr 22 '15 at 16:46
  • I have got the information of objc_sync_enter and exit from Apple's document . objc_sync_enter(id obj) : Begin synchronizing on 'obj'. Allocates recursive pthread_mutex associated with 'obj' if needed. int objc_sync_exit(id obj): End synchronizing on 'obj'. – YongUn Choi Apr 22 '15 at 16:53
  • 1
    just for others, I would suggest a more robust version `func synchronized(object:AnyObject!, @noescape _ closure: () throws -> ()) rethrows { objc_sync_enter(object) defer { objc_sync_exit(object) } try closure() }` – Leonid Usov Feb 07 '16 at 23:38
  • See even more generic option in [this answer](http://stackoverflow.com/a/34173952/5171225) – Leonid Usov Feb 07 '16 at 23:44
  • 1
    This is great for simple things, but if you want to do an early-return inside the lock, you can't (your return just returns from the lock scope, not from the `foo()` function. Unfortunate :-( – Orion Edwards Oct 01 '18 at 08:48
6

There is no native locking tools, but there are workarounds like explained in this SO question:

What is the Swift equivalent to Objective-C's "@synchronized"?

Using one of the answers, you can create a function:

    func synchronize(lockObj: AnyObject!, closure: ()->()){
        objc_sync_enter(lockObj)
        closure()
        objc_sync_exit(lockObj)
    }

and then:

     func foo() {
        var sum = 0
        var pendingElements = 10

        for i in 0 ..< 10 {
            processElementAsync(i) { value in

                synchronize(pendingElements) {
                    sum += value
                    pendingElements--

                    if pendingElements == 0 {
                        println(sum)
                    }
                }

            }
        }
    }
Community
  • 1
  • 1
Giordano Scalzo
  • 6,312
  • 3
  • 31
  • 31