我在OS X上的Qt App中有一个后台线程,用于收集数据。线程应该在每次迭代之间休眠100毫秒,但它并不总是正常工作。当应用程序是最顶级的OS X应用程序时,睡眠工作正常。但是当它不是时,睡眠持续任意时间,最多约10秒,在操作约一分钟后。
这是一个简单的Cocoa应用程序,用于演示问题(注意.mm为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
标准输出:
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
另一方面,以下完整程序不会减慢速度:
#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
标准输出:
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
我原来的应用程序是QML,也显示出相同的减速行为。
TestSleep.pro:
QT += quick
CONFIG += c++11
SOURCES += \
main.cpp
RESOURCES += qml.qrc
main.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
}
}
}
}
main.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;
}
标准输出:
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
...
使用std :: thread而不是QThread(在代码中注释掉)时,行为是相同的。
答案 0 :(得分:3)
您所看到的是Apple省电App Nap功能的效果。
你可以通过运行Apple的Activity Manager程序并查看&#34; App Nap&#34;来验证它是App Nap是罪魁祸首。列(您可能需要右键单击流程表的标题栏以使该列首先可见)。如果你的程序被app-napped,你会看到&#34;是&#34;在该列中为您的程序在表中的行。
如果您想以编程方式禁用程序的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"];
}
}
答案 1 :(得分:1)
这是由App Nap引起的,我可以在macOS 10.13.4上重现这个问题。当reproduce
设置为true
时,下面的示例将重现该示例。设置为false
时,LatencyCriticalLock
会确保App Nap无效。
另请注意,睡眠不能确保您的操作在规定的时间段内运行 - 如果操作需要任何时间,即使由于系统负载和延迟,该时间段也会比预期的长。大多数平台上的系统计时器确保平均周期正确。基于睡眠的起搏将始终以比期望更长的时间运行。
// https://github.com/KubaO/stackoverflown/tree/master/questions/appnap-49677034
#if !defined(__APPLE__)
#error This example is for macOS
#endif
#include <QtWidgets>
#include <mutex>
#include <objc/runtime.h>
#include <objc/message.h>
// see https://stackoverflow.com/a/49679984/1329652
namespace detail { struct LatencyCriticalLock {
int count = {};
id activity = {};
id processInfo = {};
id reason = {};
std::unique_lock<std::mutex> mutex_lock() {
init();
return std::unique_lock<std::mutex>(mutex);
}
private:
std::mutex mutex;
template <typename T> static T check(T i) {
return (i != nil) ? i : throw std::runtime_error("LatencyCrticalLock init() failed");
}
void init() {
if (processInfo != nil) return;
auto const NSProcessInfo = check(objc_getClass("NSProcessInfo"));
processInfo = check(objc_msgSend((id)NSProcessInfo, sel_getUid("processInfo")));
reason = check(objc_msgSend((id)objc_getClass("NSString"), sel_getUid("alloc")));
reason = check(objc_msgSend(reason, sel_getUid("initWithUTF8String:"), "LatencyCriticalLock"));
}
}; }
class LatencyCriticalLock {
static detail::LatencyCriticalLock d;
bool locked = {};
public:
struct NoLock {};
LatencyCriticalLock &operator=(const LatencyCriticalLock &) = delete;
LatencyCriticalLock(const LatencyCriticalLock &) = delete;
LatencyCriticalLock() { lock(); }
explicit LatencyCriticalLock(NoLock) {}
~LatencyCriticalLock() { unlock(); }
void lock() {
if (locked) return;
auto l = d.mutex_lock();
assert(d.count >= 0);
if (!d.count) {
assert(d.activity == nil);
/* Start activity that tells App Nap to mind its own business: */
/* NSActivityUserInitiatedAllowingIdleSystemSleep */
/* | NSActivityLatencyCritical */
d.activity = objc_msgSend(d.processInfo, sel_getUid("beginActivityWithOptions:reason:"),
0x00FFFFFFULL | 0xFF00000000ULL, d.reason);
assert(d.activity != nil);
}
d.count ++;
locked = true;
assert(d.count > 0 && locked);
}
void unlock() {
if (!locked) return;
auto l = d.mutex_lock();
assert(d.count > 0);
if (d.count == 1) {
assert(d.activity != nil);
objc_msgSend(d.processInfo, sel_getUid("endActivity:"), d.activity);
d.activity = nil;
locked = false;
}
d.count--;
assert(d.count > 0 || d.count == 0 && !locked);
}
bool isLocked() const { return locked; }
};
detail::LatencyCriticalLock LatencyCriticalLock::d;
int main(int argc, char *argv[]) {
struct Thread : QThread {
bool reproduce = {};
void run() override {
LatencyCriticalLock lock{LatencyCriticalLock::NoLock()};
if (!reproduce)
lock.lock();
const int period = 100;
QElapsedTimer el;
el.start();
QTimer timer;
timer.setTimerType(Qt::PreciseTimer);
timer.start(period);
connect(&timer, &QTimer::timeout, [&el]{
auto const duration = el.restart();
if (duration >= 1.1*period) qWarning() << duration << " ms";
});
QEventLoop().exec();
}
~Thread() {
quit();
wait();
}
} thread;
QApplication app{argc, argv};
thread.reproduce = false;
thread.start();
QPushButton msg;
msg.setText("Click to close");
msg.showMinimized();
msg.connect(&msg, &QPushButton::clicked, &msg, &QWidget::close);
return app.exec();
}
答案 2 :(得分:0)
在这种情况下工作的另一种解决方案是使用c函数pthread_setschedparam增加线程优先级,如果由于某种原因你想要一个Naps而后台线程没有的应用程序。
int priority_max = sched_get_priority_max(SCHED_RR);
struct sched_param sp;
sp.sched_priority = priority_max;
pthread_setschedparam(_collectorThread.native_handle(), SCHED_RR, &sp);