简体   繁体   中英

Implementation of swift's synchronized not working

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 synchronized implementation 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 )

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.

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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM