线程在后台运行时在 OS X 上睡眠时间过长

Col*_*lin 1 macos qt multithreading sleep

我在 OS X 上的 Qt 应用程序中有一个后台线程,用于收集数据。线程应该在每次迭代之间休眠 100 毫秒,但它并不总是正常工作。当应用程序是最顶层的 OS X 应用程序时,睡眠工作正常。但如果不是,睡眠会持续任意时间,最多约 10 秒,大约运行一分钟后。

这是一个演示问题的简单 Cocoa 应用程序(注意 .mm for objc++)

AppDelegate.mm:

#import "AppDelegate.h"
#include <iostream>
#include <thread>
#include <libgen.h>
using namespace std::chrono;

#define DEFAULT_COLLECTOR_SAMPLING_FREQUENCY 10

namespace Helpers {
  uint64_t time_ms() {
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
  }
}

std::thread _collectorThread;
bool _running;

@interface AppDelegate ()

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  _running = true;
  uint64_t start = Helpers::time_ms();
  _collectorThread =
  std::thread (
               [&]{
                 while(_running) {
                   uint64_t t1, t2;
                   t1 = Helpers::time_ms();
                   std::this_thread::sleep_for((std::chrono::duration<int, std::milli>)(1000 / DEFAULT_COLLECTOR_SAMPLING_FREQUENCY));
                   t2 = Helpers::time_ms();
                   std::cout << (int)((t1 - start)/1000) << " TestSleep: sleep lasted " << t2-t1 << " ms" << std::endl;
                 }
               });    
}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
  _running = false;
  _collectorThread.join();
}


@end
Run Code Online (Sandbox Code Playgroud)

标准输出:

0 TestSleep: sleep lasted 102 ms.  // Window is in background
0 TestSleep: sleep lasted 101 ms.  // behind Xcode window
0 TestSleep: sleep lasted 104 ms
0 TestSleep: sleep lasted 104 ms
0 TestSleep: sleep lasted 105 ms
0 TestSleep: sleep lasted 105 ms
0 TestSleep: sleep lasted 105 ms
0 TestSleep: sleep lasted 104 ms
0 TestSleep: sleep lasted 102 ms
0 TestSleep: sleep lasted 102 ms
1 TestSleep: sleep lasted 105 ms
1 TestSleep: sleep lasted 105 ms
1 TestSleep: sleep lasted 104 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 100 ms
...
...
52 TestSleep: sleep lasted 102 ms
52 TestSleep: sleep lasted 101 ms
52 TestSleep: sleep lasted 104 ms
52 TestSleep: sleep lasted 105 ms
52 TestSleep: sleep lasted 104 ms
52 TestSleep: sleep lasted 100 ms
52 TestSleep: sleep lasted 322 ms. // after ~1 minute,
53 TestSleep: sleep lasted 100 ms. // sleep gets way off
53 TestSleep: sleep lasted 499 ms
53 TestSleep: sleep lasted 1093 ms
54 TestSleep: sleep lasted 1086 ms
56 TestSleep: sleep lasted 1061 ms
57 TestSleep: sleep lasted 1090 ms
58 TestSleep: sleep lasted 1100 ms
59 TestSleep: sleep lasted 1099 ms
60 TestSleep: sleep lasted 1096 ms
61 TestSleep: sleep lasted 390 ms
61 TestSleep: sleep lasted 100 ms
61 TestSleep: sleep lasted 102 ms   // click on app window
62 TestSleep: sleep lasted 102 ms  // to bring it to foreground
62 TestSleep: sleep lasted 105 ms
Run Code Online (Sandbox Code Playgroud)

另一方面,以下完整程序不会减慢:

#include <iostream>
#include <thread>
#include <libgen.h>
using namespace std::chrono;

#define DEFAULT_COLLECTOR_SAMPLING_FREQUENCY 10

namespace Helpers {
    uint64_t time_ms() {
        return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
    }
}

int main(int argc, char *argv[])
{
    bool _running = true;
    uint64_t start = Helpers::time_ms();
    std::thread collectorThread = std::thread (
                [&]{
        while(_running) {
            uint64_t t1, t2;
            t1 = Helpers::time_ms();
            std::this_thread::sleep_for((std::chrono::duration<int, std::milli>)(1000 / DEFAULT_COLLECTOR_SAMPLING_FREQUENCY));
            t2 = Helpers::time_ms();
            std::cout << (int)((t1 - start)/1000) << " TestSleep: sleep lasted " << t2-t1 << " ms" << std::endl;
        }
    });
    collectorThread.join();
    return 0;
}

// clang++ -std=c++14 -o testc++ main.cpp 
Run Code Online (Sandbox Code Playgroud)

标准输出:

0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 105 ms
0 TestSleep: sleep lasted 105 ms
0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 104 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 104 ms
1 TestSleep: sleep lasted 102 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 100 ms
...
...
99 TestSleep: sleep lasted 101 ms
99 TestSleep: sleep lasted 105 ms
99 TestSleep: sleep lasted 104 ms
100 TestSleep: sleep lasted 104 ms
100 TestSleep: sleep lasted 101 ms
100 TestSleep: sleep lasted 104 ms
Run Code Online (Sandbox Code Playgroud)

我原来的应用程序是 QML,也表现出同样的减速行为。

TestSleep.pro:

QT += quick
CONFIG += c++11
SOURCES += \
        main.cpp
RESOURCES += qml.qrc
Run Code Online (Sandbox Code Playgroud)

主.qml:

import QtQuick 2.9
import QtQuick.Controls 2.2

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Scroll")

    ScrollView {
        anchors.fill: parent

        ListView {
            width: parent.width
            model: 20
            delegate: ItemDelegate {
                text: "Item " + (index + 1)
                width: parent.width
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

主.cpp:

#define DEFAULT_COLLECTOR_SAMPLING_FREQUENCY 10

namespace Helpers {
    uint64_t time_ms() {
        return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
    }
}

int main(int argc, char *argv[])
{
    bool _running = true;
    QThread *collectorThread = QThread::create(
//    std::thread collectorThread = std::thread (
                [&]{
        while(_running) {
            uint64_t t1;
            t1 = Helpers::time_ms();
            QThread::msleep(1000 / DEFAULT_COLLECTOR_SAMPLING_FREQUENCY);
//            std::this_thread::sleep_for((std::chrono::duration<int, std::milli>)(1000 / DEFAULT_COLLECTOR_SAMPLING_FREQUENCY));
            t1 = Helpers::time_ms() - t1;
            std::cout << "TestUSleep: sleep lasted " << t1 << " ms" << std::endl;
        }
    });
    collectorThread->start();
    collectorThread->setPriority(QThread::TimeCriticalPriority);

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    int returnValue = app.exec();
//    collectorThread.join();
    collectorThread->quit();
    collectorThread->wait();
    collectorThread->deleteLater();
    return returnValue;
}
Run Code Online (Sandbox Code Playgroud)

标准输出:

0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 102 ms
0 TestSleep: sleep lasted 100 ms
0 TestSleep: sleep lasted 102 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 101 ms
0 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 100 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 101 ms
1 TestSleep: sleep lasted 101 ms
...
...
63 TestSleep: sleep lasted 100 ms
63 TestSleep: sleep lasted 101 ms
63 TestSleep: sleep lasted 102 ms
63 TestSleep: sleep lasted 101 ms
63 TestSleep: sleep lasted 101 ms
63 TestSleep: sleep lasted 7069 ms  # slows down
70 TestSleep: sleep lasted 235 ms
70 TestSleep: sleep lasted 10100 ms
80 TestSleep: sleep lasted 7350 ms
88 TestSleep: sleep lasted 10100 ms
98 TestSleep: sleep lasted 3566 ms
101 TestSleep: sleep lasted 100 ms
102 TestSleep: sleep lasted 3242 ms
105 TestSleep: sleep lasted 2373 ms
107 TestSleep: sleep lasted 100 ms  # click on main window
107 TestSleep: sleep lasted 101 ms  # to put app on top
107 TestSleep: sleep lasted 101 ms  # and back to normal
107 TestSleep: sleep lasted 101 ms  # behavior
108 TestSleep: sleep lasted 101 ms
108 TestSleep: sleep lasted 102 ms
...
Run Code Online (Sandbox Code Playgroud)

当使用 std::thread 而不是 QThread (在代码中注释掉)时,行为是相同的。

Jer*_*ner 5

你看到的是苹果的省电App Nap功能的效果。

您可以通过运行 Apple 的 Activity Manager 程序并查看“App Nap”列(您可能需要右键单击进程表的标题栏以首先使该列可见)来验证是否是 App Nap 是罪魁祸首。如果您的程序正在应用程序中,您将在该列中看到表中程序行的“是”。

如果您想以编程方式为您的程序禁用 app-nap,您可以将此 Objective-C++ 文件放入您的程序并调用 main() 顶部的 disable_app_nap() 函数:

#import <Foundation/Foundation.h>
#import <Foundation/NSProcessInfo.h>

void disable_app_nap(void)
{
   if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:reason:)])
   {
      [[NSProcessInfo processInfo] beginActivityWithOptions:0x00FFFFFF reason:@"Not sleepy and don't want to nap"];
   }
}
Run Code Online (Sandbox Code Playgroud)