3

I have been using "usleep" to stop a thread during some milliseconds and I have checked that is stopping more time than expected.

I am sure I am doing something wrong, I am not an expert in Swift, but I don't understand it because it is very easy to check. For example:

DispatchQueue.global(qos: .background).async {
    let timeStart: Int64 = Date().toMillis()
    usleep(20 * 1000) // 20 ms
    let timeEnd: Int64 = Date().toMillis()
    let timeDif = timeEnd - timeStart
    print("start: \(timeStart), end: \(timeEnd), dif: \(timeDif)")
}

The result:

start: 1522712546115, end: 1522712546235, dif: 120

If I execute the same in the main thread:

start: 1522712586996, end: 1522712587018, dif: 22

I think the way I generate the thread is wrong for stopping it. How could I generate a thread that works good with usleep?

Thanks

rmaddy
  • 314,917
  • 42
  • 532
  • 579
J.S.R - Silicornio
  • 1,111
  • 13
  • 16
  • Thanks. I don't need high precision but from 20 to 120 there is a big difference. I will check the Apple docs. – J.S.R - Silicornio Apr 03 '18 at 00:08
  • "How could I generate a thread that works good with usleep?" You should almost always avoid usleep. A queue is not like a thread, and this is almost never the right tool for iOS. Go back to the problem you're trying to solve, and we can discuss how to do that with GCD. If you're familiar with threads, see "Migrating Away from Threads" in the Concurrency Programming Guide for how to perform similar things in GCD. https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ThreadMigration/ThreadMigration.html#//apple_ref/doc/uid/TP40008091-CH105-SW1 – Rob Napier Apr 03 '18 at 22:20
  • 20ms also falls into what I consider the "suspicious value zone." It's way too fast for batch processing, a bit too fast for debouncing, but too sloppy for animations or sound generation. It's the kind of value that often suggests you're approaching the problem incorrectly. – Rob Napier Apr 03 '18 at 22:32
  • Thanks for all comments and responses. I will check all information you gave me to afford the problem in the best way. I need 20-30ms delays because I am painting a real time chart from a Bluetooth device that is sending continuously information. I don't want to be painting all the time, so I paint small packages. – J.S.R - Silicornio Apr 03 '18 at 23:02
  • Yeah; definitely don't want a 20ms timer for that. Look at `CADisplayLink`; it's probably ideal for this problem. – Rob Napier Apr 03 '18 at 23:45

2 Answers2

2

A couple of thoughts:

  1. The responsiveness to usleep is a function of the Quality of Service of the queue. For example, doing thirty 20ms usleep calls on the five queue types, resulting in the following average and standard deviations (measured in ms):

    QoS               mean  stdev
    ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   ‐‐‐‐‐ ‐‐‐‐‐
    background        99.14 28.06
    utility           74.12 23.66
    default           23.88  1.83
    userInitiated     22.09  1.87
    userInteractive   20.99  0.29
    

    The higher the quality of service, the closer to 20 ms it got, and with a lower standard deviation.

  2. If you want accurate time measurements, you should avoid using Date and/or CFAbsoluteTimeGetCurrent(). As the documentation warns us:

    Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.

    You can use a mach_time based value, such as conveniently returned by CACurrentMediaTime(), to avoid this problem. For example:

    let start = CACurrentMediaTime()
    // do something
    let elapsed = CACurrentMediaTime() - start
    
  3. If you need even higher accuracy, see Apple Technical Q&A #2169.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

I, too, get times around 120 ms when sleeping a thread on a background queue:

import Foundation

DispatchQueue.global(qos: .background).async {
    let timeStart = Date()
    usleep(20 * 1000) // 20 ms
    let timeEnd = Date()
    let timeDif = timeEnd.timeIntervalSince(timeStart) * 1000
    print("start: \(timeStart), end: \(timeEnd), dif: \(timeDif)")
    exit(0)
}

CFRunLoopRun()

outputs:

start: 2018-04-03 00:10:54 +0000, end: 2018-04-03 00:10:54 +0000, dif: 119.734048843384

However, with default QoS, my results are consistently closer to 20 ms:

import Foundation

DispatchQueue.global(qos: .default).async {
    let timeStart = Date()
    usleep(20 * 1000) // 20 ms
    let timeEnd = Date()
    let timeDif = timeEnd.timeIntervalSince(timeStart) * 1000
    print("start: \(timeStart), end: \(timeEnd), dif: \(timeDif)")
    exit(0)
}

CFRunLoopRun()

start: 2018-04-03 00:12:15 +0000, end: 2018-04-03 00:12:15 +0000, dif: 20.035982131958

So it appears that the .background QoS is causing the behavior you are seeing. Although I don't have direct knowledge of why this would be, it doesn't seem too far-fetched to speculate that the OS may be somewhat more lax about waking up sleeping threads that are marked with a background QoS. Indeed, this is what Apple's documentation has to say about it:

A quality of service (QoS) class allows you to categorize work to be performed by NSOperation, NSOperationQueue, NSThread objects, dispatch queues, and pthreads (POSIX threads). By assigning a QoS to work, you indicate its importance, and the system prioritizes it and schedules it accordingly. For example, the system performs work initiated by a user sooner than background work that can be deferred until a more optimal time. In some cases, system resources may be reallocated away from the lower priority work and given to the higher priority work.

The possibility for your background work to be "deferred until a more optimal time" seems a plausible explanation of the behavior you're seeing.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60