-1

I am new to swift and I am trying my hands with multithreading, a concept which doesn't seem to be famous in swift. Based on this example of java's synchronizedimplementation I tried to do the same for swift based on examples given for swift on another SO post. Here is my implementation:

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

public class TestThread {
    var name : String;
    var theDemo : TheDemo;
    init(_ name : String, _ theDemo : TheDemo) {
        self.theDemo = theDemo;
        self.name = name;
        run()
    }

    public func run() {
        DispatchQueue.global(qos: .background).async {
            DispatchQueue.main.async {
                self.theDemo.testSynced(self.name)
            }
        }
    }
}

public class TheDemo {
    private func synced(_ lock : Any, _ name : String, _ closure : (_ name : String) -> ()){
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        closure(name)

    }
    public func testSynced(_ name : String){
        synced(self, name, test)
    }
    public func test(_ name : String) {
        for i in 0..<4 {
            let str = "\(name) :: \(i)"
            let theDeadline = DispatchTime.now() + .seconds(i/2)
            DispatchQueue.main.asyncAfter(deadline: theDeadline, execute: {
                print(str)
            })
        }
    }

}

var theDemo = TheDemo()
TestThread("THREAD 1", theDemo)
TestThread("THREAD 2", theDemo)
TestThread("THREAD 3", theDemo)
TestThread("THREAD 4", theDemo)

When I run the above code in the playground the result I get looks like the following

THREAD 1 :: 0
THREAD 1 :: 1
THREAD 2 :: 0
THREAD 2 :: 1
THREAD 3 :: 0
THREAD 3 :: 1
THREAD 4 :: 0
THREAD 4 :: 1
THREAD 1 :: 2
THREAD 1 :: 3
THREAD 2 :: 2
THREAD 2 :: 3
THREAD 3 :: 2
THREAD 3 :: 3
THREAD 4 :: 2
THREAD 4 :: 3

But I was expecting a result that looks like the following.

THREAD 1 :: 0
THREAD 1 :: 1
THREAD 1 :: 2
THREAD 1 :: 3
THREAD 2 :: 0
THREAD 2 :: 1
THREAD 2 :: 2
THREAD 2 :: 3
THREAD 3 :: 0
THREAD 3 :: 1
THREAD 3 :: 2
THREAD 3 :: 3
THREAD 4 :: 0
THREAD 4 :: 1
THREAD 4 :: 2
THREAD 4 :: 3

I wish to understand what I am doing wrong. Have I set my expectation also on the right path considering the concept of synchronisation and swift threads. I appreciate the help. thanks.

EDIT

Since I believe that I am misunderstood, I want to explain a bit more of what I want to do. What I basically want to achieve is a bit different than what I have written here in the code. This is a simplified version. I want to test it if synchronized works in a simple example and then I want to use it in another project. I am translating a project written for android to ios. In java multithreading I am using synchronized so that only one thread at a time can use that function. let's say there is a variable or a function that multiple threads can work on. When I put synchronized before the function name or the variable name, threads will have a lock. Now in my project I have several threads, that come and go, based on some sensor input - and before they die they make use of a common shared function. They could come at any point. For several reasons, we set that common function synchronized so that no two threads enter the function at the same time. Thus, when I am translating the code I looked for what would be similar in swift and I found one of the links that is linked to this question. And I tried to use that - and it didn't work for me. To err is human - and I probably have made a mistake somewhere. But I have spent time to read based on the schedule I have. (I partly said that multithreading isn't very famous in swift because of some places I have read for example this)

besabestin
  • 482
  • 8
  • 26
  • Please search on "asynchronous". This has been explained here _many many many_ times. – matt Mar 19 '18 at 16:36
  • My question was rather on the keyword synchronized and the expected output. I implemented based on the suggestions I found in stackoverflow and the result doesn't resemble that of java's. Can you please not downvote me - I d rather delete the question. Thanks. – besabestin Mar 19 '18 at 16:55
  • "I d rather delete the question" Then delete it. – matt Mar 19 '18 at 17:02
  • I have tried to include some more description. Thanks for the answer. – besabestin Mar 19 '18 at 19:35
  • “For several reasons, we set that common function synchronized so that no two threads enter the function at the same time.” Fine. So see my answer: in Cocoa and GCD, we use serial queues to accomplish the same thing. Serial queues are a form of locking — a better form. – matt Mar 19 '18 at 22:19
  • Hi @matt I want to say thanks. I was able to synchronize resource access by using serial queues. So, in my application I made use of both serial and concurrent queues. But I don't understand why there is that stackoverflow post that confused me (about the synchronized keyword that i included.) I wonder if I should delete this question or leave it as it is and accept your answer. – besabestin Mar 29 '18 at 13:06
  • Well I wish you’d do one or the other. – matt Mar 30 '18 at 13:13

2 Answers2

2

First of all, never test these things in a playground, as its output will not correctly simulate reality. Test in an actual app project.

Second, get rid of all this:

    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    closure(name)

Do whatever it is you want done purely using GCD.

I'm not clear on what you do want done; the kerfuffle with objc_sync_enter and asyncAfter is just too mystifying for me. This stuff, however, is not at all mystifying, and is very well documented and explained (contrary to your claim).

From your desired output, it looks like you want to queue the "thread 1" operation and have it run from start to finish before the "thread 2" operation starts.

  • One way to guarantee that is to queue these operations onto a serial queue, because that means that a queued operation cannot start until the queue ahead of it is empty. That's not happening in your code because you queue is a concurrent queue, meaning that the operations run at the same time (i.e. their steps can be interleaved with one another).
  • Another way would be to call the "thread 1" operation with sync rather than async, because that means your code waits (blocks) until the operation completes and returns.

Here's an example of the first way, which I think is the better way:

let queue = DispatchQueue(label:"myqueue") // serial queue
func go() {
    for i in 0..<4 {
        let name = "Thread \(i)"
        queue.async {
            for i in 0..<4 {
                let str = "\(name) :: \(i)"
                print(str)
            }
        }
    }
}

Output when we call go():

Thread 0 :: 0
Thread 0 :: 1
Thread 0 :: 2
Thread 0 :: 3
Thread 1 :: 0
Thread 1 :: 1
Thread 1 :: 2
Thread 1 :: 3
Thread 2 :: 0
Thread 2 :: 1
Thread 2 :: 2
Thread 2 :: 3
Thread 3 :: 0
Thread 3 :: 1
Thread 3 :: 2
Thread 3 :: 3

That looks a lot like what you said you wanted.

matt
  • 515,959
  • 87
  • 875
  • 1,141
1

Problem

In TheDemo

let theDeadline = DispatchTime.now() + .seconds(i/2)
DispatchQueue.main.asyncAfter(deadline: theDeadline, execute: {
    self.theDemo.testSynced(self.name)


Solution

In TestThread:

DispatchQueue.global(qos: .background).async {
        let theDeadline = DispatchTime.now() + .seconds(1)
        DispatchQueue.main.asyncAfter(deadline: theDeadline, execute: {

In TheDemo

for i in 0..<4 {
    let str = "\(name) :: \(i)"
    print(str)
}


Explanation

So. Each asynchronous task is completing so fast that they are getting deferred to the same time from now. You are getting an insight into how DispatchQueue's main thread handles the situation when you've asynced everything to the same time.

//Starts at some exact time

TestThread("THREAD 1", theDemo)
TestThread("THREAD 2", theDemo)
TestThread("THREAD 3", theDemo)
TestThread("THREAD 4", theDemo)

//Finishes at the same exact time.  Too fast!
//

In my solution, the printing is in a single time context (the playground's main queue where you create all your custom threads). So it prints as you expect. You'll have to use a much finer unit (there isn't [nanoseconds]) or design it slightly differently like I showed you.

Benjamin
  • 1,832
  • 1
  • 17
  • 27
  • Hmm... unfortunately, the deferring is there for a reason. I mean.. in the java example I provided they give Thread.sleep(500ms). This is so that the threads will mix when the other threads are taking long time. (if the deferring isn't there - eventhough there is multithreading - they would most probably perform sequentially.) The deferring is important to force so that the threads mix. Then, when synchronized keyword is given - they will just wait eventhough there was enough time for concurrent execution. do you get my point? – besabestin Mar 19 '18 at 16:07
  • @besabestin You just have to move when you defer it, see my edited answer. Sorry I didn't make that clear at first. – Benjamin Mar 19 '18 at 16:14
  • @besabestin If you think about it from an object design concern standpoint, the `TestThread` should have control over when it defers an asynchronous run to, not `TheDemo` because `TestThread` is in charge of time in it's own personal universe (the background thread). – Benjamin Mar 19 '18 at 16:23