不时见到有人会这样做:
- 不使用QThread,而是使用pthread等平台相关的底层 api
- 而又不想使用底层线程间同步、通讯的api
那么,如何使用pthread,而又使用Qt提供的线程间机制呢?
本文的初衷源于此,但是使用的的是C++0x 的 std::thread,而不是直接使用unix的pthread。(既然用Qt,还是尽量保证夸平台吧)
不想写太多的文字,还是用一个一个的小例子来说话吧。
例子一
- 界面上一个QSpinBox
-
次线程中每隔一段时间会生成一个值,传递给该SpinBox
#include <QtGui/QApplication> #include <QtGui/QSpinBox> #include <thread> void test1(QSpinBox * w) { for (int i=0; i<10; ++i) { msSleep(1000); QMetaObject::invokeMethod(w, "setValue", Q_ARG(int, i*i)); } } int main(int argc, char *argv[]) { QApplication a(argc, argv); QSpinBox w; w.show(); std::thread t1(test1, &w); return a.exec(); }
其中 msSleep 是我们自定义的一个sleep函数:
void msSleep(int ms) { #ifdef Q_OS_WIN Sleep(uint(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, NULL); #endif }
invokeMethod?
为什么要用蹩脚的invokeMethod?
QMetaObject::invokeMethod(w, "setValue", Q_ARG(int, i*i));
而不是直接用
w.setValue(i*i);
- Manual中说的明白:所有的GUI部件都是不可重入的!
QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.
-
不要去挑战它的权威!注意:在这个例子中,如果你直接用 w.setValue(i*i);,在绝大多数情况下你可能都能得到正确的结果,但是并不能证明这么做是对的。
例子二
次线程到主线程的通讯,前面用的是invokeMethod。有无其他办法呢?
其实在多线程情况下,无论是invokeMethod还是signal-slot,都是通过Qt的事件系统来完成的。
看Manual,注意Note部分:
void QCoreApplication::postEvent ( QObject * receiver, QEvent * event ) [static] Note: This function is thread-safe.
所以,我们可以直接使用这个它(通过自定义事件来传递信息):
#include <QtGui/QApplication> #include <QtGui/QSpinBox> #include <thread> class Event:public QEvent { public: Event(int value):QEvent(QEvent::User), value(value){} int value; }; class SpinBox:public QSpinBox { public: SpinBox(){} protected: bool event(QEvent *event) { if (event->type() == QEvent::User){ Event * evt = static_cast<Event*>(event); setValue(evt->value); } return QSpinBox::event(event); } }; void test1(QSpinBox * w) { for (int i=0; i<10; ++i) { msSleep(1000); qApp->postEvent(w, new Event(i*i)); } } int main(int argc, char *argv[]) { QApplication a(argc, argv); SpinBox w; w.show(); std::thread t1(test1, &w); return a.exec(); }
例子三
看一个次线程对象发送信号到主线程对象的例子:
#include <QtGui/QApplication> #include <QtGui/QSpinBox> #include <thread> class Object:public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) public: Object():m_val(0){} int value() const{return m_val;} public slots: void setValue(int v) { if (m_val != v){ m_val = v; emit valueChanged(v); } } signals: void valueChanged(int v); private: int m_val; }; void test1(QSpinBox * w) { Object obj; obj.connect(&obj, SIGNAL(valueChanged(int)), w, SLOT(setValue(int))); for (int i=0; i<10; ++i) { msSleep(1000); obj.setValue(i*i); } } int main(int argc, char *argv[]) { QApplication a(argc, argv); QSpinBox w; w.show(); std::thread t1(test1, &w); return a.exec(); }
例子本身没有多少可多说的。当次线程中Object对象的值发生变化是,会发送信号,由于信号connect到主线程的SpinBox中,所以就看到和例子一例子二完全一致的效果了。