1

Say we have multiple threads issuing prints. Typically when downloading stuffs as follows:

let url = self.url
print("loadPreview(\(source) for \(url)): ↝start loading \(self.url")
let task = session.downloadTask(with: url) { 
    (localUrl, response, error) in
    print("loadPreview(\(source) for \(url)): == \(self.url")
}

Is there any way to make print atomic and prevent output as follows?

loadPreview(WSJ for www.wsj.co⟷TloadPreview(9loadPreview(appleins   for appleinsid⟷n-messages):     ↝start loading http://app⟷n-messages
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • Possible duplicate of [Python 2.7: Print thread safe](https://stackoverflow.com/questions/7877850/python-2-7-print-thread-safe) – John Anderson Nov 03 '17 at 22:46

2 Answers2

2

A quick hack is just to use a NSLock around your print. For instance, you could define an atomic print function as:

private let printLock = NSLock()

func aprint(_ message: String){
    printLock.lock()
    defer { printLock.unlock() }
    print(message)
}

and use it like the standard print function:

aprint(“This will print atomically!”)

You could also achieve a similar result using a serial DispatchQueue to serialize your print calls as well. For instance:

private let printQueue = DispatchQueue(label: "aprint", qos: .utility)

func aprint(_ message: String){
    printQueue.async {
        print(message)
    }
}

This solution provides better performance mainly thanks to the async call. This ensures the calling thread will not block until the lock is acquired (and the corresponding print is completely performed). But, to be clear, this solution is also perfectly atomic as well.

(For the record, simply using DispatchQueue.main might behave weirdly if the calling code is also running on the main queue.)

I would recommend picking the second solution ;)

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
  • Will this solution differs from what I already tested?, aka: objc_sync_enter(self); print(text); objc_sync_exit(self); – Stéphane de Luca Nov 03 '17 at 23:15
  • Same result *if* all `print` calls use the same object as lock (i.e., the `self` var your code). Otherwise you might still see mixed outputs when printing from different objects. Anyway, I recommend you wrap the selected solution in nice API to also improve code readability;) – Paulo Mattos Nov 03 '17 at 23:25
  • ...but the `DispatchQueue` based solution might provide a slightly better performance ;) – Paulo Mattos Nov 03 '17 at 23:34
  • So I understand why the prints still mix, the self is not a single instance. Would you mind to modify your answer and show me how the DispatchQueue solution looks like? – Stéphane de Luca Nov 03 '17 at 23:38
  • 1
    Thanks @Paulo I picked the DispatchQueue solution up – Stéphane de Luca Nov 03 '17 at 23:49
  • Well, that's weird: on an real device, I have the same results than before: I tried the two methods: 2-loadPreview(Mac4eve2-loadPreview(i1-loadPreview(cnet for www.cnet.c⟷CAD590a51e : ↝start loading www.cnet.c⟷CAD590a51e I really don"'t get it – Stéphane de Luca Nov 04 '17 at 00:52
  • @StéphanedeLuca Are you defining a *single* `printQueue` in your codebase? Also double check that your device deployment workflow is working as expected... – Paulo Mattos Nov 04 '17 at 00:55
0

You can use DispatchIO to automatically serialise the output to either stdout or even a file in a non-blocking fashion. It's a bit more work and you may want to wrap the example below into a class:

import Foundation

// Open stdout with DispatchIO
let queue = DispatchQueue.global()
let fd = FileHandle.standardOutput.fileDescriptor
let writer = DispatchIO(type: .stream, fileDescriptor: fd, queue: queue) { (errno) in
  if errno != 0 {
    print("Cannot open stdout: \(errno)")
  }
}

// Encode string
if let data = string.data(using: .utf8) {
  data.withUnsafeBytes { (buffer) in
    // Produce DispatchData with encoded string
    let dispatchData = DispatchData(bytes: buffer)

    // Print string to stdout
    writer.write(offset: .zero, data: dispatchData, queue: queue) { (done, data, errno) in
      if errno != 0 {
        print("Write error: \(errno)")
      }
    }
  }
}

// Close the DispatchIO when you don't need it anymore
writer.close()
pronebird
  • 12,068
  • 5
  • 54
  • 82