2012-07-01 39 views
17

Tôi có một số QThread tạo ra một lượng lớn dữ liệu thường xuyên (vài megabyte mỗi giây) và cần truyền dữ liệu đó tới chuỗi chủ (GUI).Gửi số lượng lớn dữ liệu giữa các chuỗi Qt

Tôi sợ rằng tôi không chắc chắn trong hoạt động bên trong của QThread vì vậy tôi muốn yêu cầu thực hành tốt nhất.

Rõ ràng, cách trực tiếp nhất để truyền dữ liệu là chỉ emit một mảng. Tuy nhiên, hiệu quả như thế nào? Qt có biết về nơi nó được sử dụng và tránh sao chép sâu nó khi gửi và nhận không?

Nếu không, tôi có thể sẵn sàng cấp phát bộ nhớ trong chuỗi chính và đưa con trỏ đến chuỗi con nơi nó sẽ ghi dữ liệu (và chỉ emit thông báo ngắn về tiến trình). Đây không phải là giải pháp thanh lịch nhất đối với tôi, đây là lý do tại sao tôi hỏi.

Nếu Qt tránh sao chép dữ liệu trong nhiều bộ đệm khi phát và nhận, nó có được đảm bảo trong tất cả các hệ thống không? Tôi không có tài nguyên để thử điểm chuẩn trong các hệ điều hành khác nhau.

+0

Chúng ta cần phải tìm hiểu thêm. Có thể chấp nhận chủ đề chính để mất dữ liệu không? Chủ đề chính làm gì với dữ liệu? Nhưng bất kể nhu cầu của bạn, tôi không thể tin rằng phát ra một mảng là giải pháp tối ưu. – TonyK

Trả lời

32

QThread hoạt động bên trong của chúng không liên quan: chúng không đóng vai trò gì trong cách vòng lặp sự kiện hoạt động. Khi bạn emit tín hiệu trong một QObject sống trong một chủ đề khác với đối tượng của vị trí, tín hiệu sẽ được đăng dưới dạng QMetaCallEvent vào hàng đợi sự kiện của chuỗi nhận. Vòng lặp sự kiện đang chạy trong chuỗi nhận sẽ sau đó hành động trên sự kiện này và thực hiện cuộc gọi vào khe được kết nối với tín hiệu phát ra. Vì vậy, không có vấn đề gì xảy ra, bất cứ dữ liệu nào bạn gửi qua tín hiệu cuối cùng sẽ kết thúc dưới dạng tải trọng trong một thể hiện của lớp dẫn xuất từ ​​QEvent.

Thịt của vấn đề là khi QMetaCallEvent đến vòng lặp sự kiện và vùng chứa được chuyển vào vị trí làm đối số. Tất nhiên các nhà xây dựng bản sao có thể được gọi là nhiều lần trên đường đi.Dưới đây là một số mã đơn giản mà thể hiện bao nhiêu lần so với constructor sao chép và mặc định constructor được trong thực tế gọi là

  • trên các yếu tố của các thành viên dữ liệu của một container ngầm chia sẻ copy-on-write (QVector),

  • trên lớp tùy chỉnh phù hợp cho vùng chứa.

Bạn sẽ được ngạc nhiên :)

Kể từ container Qt đang ngầm chia sẻ copy-on-write, xây dựng bản sao của họ có chi phí không đáng kể: tất cả đã xong là một bộ đếm tham chiếu được tăng lên nguyên tử xây dựng . Chẳng hạn, không có thành viên dữ liệu nào được sao chép.

Than ôi, trước 11 C++ cho thấy mặt xấu của nó: nếu mã vùng sửa đổi vùng chứa theo bất kỳ cách nào, không có cách nào chuyển tham chiếu đến vị trí theo cách sao cho trình biên dịch biết rằng vùng chứa ban đầu là không cần nữa. Do đó: nếu khe nhận được tham chiếu const đến vùng chứa, bạn được đảm bảo rằng sẽ không có bản sao nào được tạo. Nếu vị trí nhận được bản sao có thể ghi của vùng chứa bạn sửa đổi nó, sẽ có bản sao hoàn toàn không cần thiết được thực hiện vì phiên bản còn lại tại trang cuộc gọi không còn cần thiết nữa. Trong C++ - 11 bạn sẽ chuyển một tham chiếu rvalue làm tham số. Việc chuyển một tham chiếu rvalue trong một cuộc gọi hàm kết thúc vòng đời của đối tượng được truyền trong người gọi. đầu ra mã

mẫu:

"Started" copies: 0 assignments: 0 default instances: 0 
"Created Foo" copies: 0 assignments: 0 default instances: 100 
"Created Bar" copies: 0 assignments: 0 default instances: 100 
"Received signal w/const container" copies: 0 assignments: 0 default instances: 100 
"Received signal w/copy of the container" copies: 0 assignments: 0 default instances: 100 
"Made a copy" copies: 100 assignments: 1 default instances: 101 
"Reset" copies: 0 assignments: 0 default instances: 0 
"Received signal w/const class" copies: 2 assignments: 0 default instances: 1 
"Received signal w/copy of the class" copies: 3 assignments: 0 default instances: 1 
//main.cpp 
#include <QtCore> 

class Class { 
    static QAtomicInt m_copies; 
    static QAtomicInt m_assignments; 
    static QAtomicInt m_instances; 
public: 
    Class() { m_instances.fetchAndAddOrdered(1); } 
    Class(const Class &) { m_copies.fetchAndAddOrdered(1); } 
    Class & operator=(const Class &) { m_assignments.fetchAndAddOrdered(1); return *this; } 
    static void dump(const QString & s = QString()) { 
     qDebug() << s << "copies:" << m_copies << "assignments:" << m_assignments << "default instances:" << m_instances; 
    } 
    static void reset() { 
     m_copies = 0; 
     m_assignments = 0; 
     m_instances = 0; 
    } 
}; 

QAtomicInt Class::m_instances; 
QAtomicInt Class::m_copies; 
QAtomicInt Class::m_assignments; 

typedef QVector<Class> Vector; 

Q_DECLARE_METATYPE(Vector) 

class Foo : public QObject 
{ 
    Q_OBJECT 
    Vector v; 
public: 
    Foo() : v(100) {} 
signals: 
    void containerSignal(const Vector &); 
    void classSignal(const Class &); 
public slots: 
    void sendContainer() { emit containerSignal(v); } 
    void sendClass() { emit classSignal(Class()); } 
}; 

class Bar : public QObject 
{ 
    Q_OBJECT 
public: 
    Bar() {} 
signals: 
    void containerDone(); 
    void classDone(); 
public slots: 
    void containerSlotConst(const Vector &) { 
     Class::dump("Received signal w/const container"); 
    } 
    void containerSlot(Vector v) { 
     Class::dump("Received signal w/copy of the container"); 
     v[99] = Class(); 
     Class::dump("Made a copy"); 
     Class::reset(); 
     Class::dump("Reset"); 
     emit containerDone(); 
    } 
    void classSlotConst(const Class &) { 
     Class::dump("Received signal w/const class"); 
    } 
    void classSlot(Class) { 
     Class::dump("Received signal w/copy of the class"); 
     emit classDone(); 
     //QThread::currentThread()->quit(); 
    } 
}; 

int main(int argc, char ** argv) 
{ 
    QCoreApplication a(argc, argv); 
    qRegisterMetaType<Vector>("Vector"); 
    qRegisterMetaType<Class>("Class"); 

    Class::dump("Started"); 
    QThread thread; 
    Foo foo; 
    Bar bar; 
    Class::dump("Created Foo"); 
    bar.moveToThread(&thread); 
    Class::dump("Created Bar"); 
    QObject::connect(&thread, SIGNAL(started()), &foo, SLOT(sendContainer())); 
    QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlotConst(Vector))); 
    QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlot(Vector))); 
    QObject::connect(&bar, SIGNAL(containerDone()), &foo, SLOT(sendClass())); 
    QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlotConst(Class))); 
    QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlot(Class))); 
    QObject::connect(&bar, SIGNAL(classDone()), &thread, SLOT(quit())); 
    QObject::connect(&thread, SIGNAL(finished()), &a, SLOT(quit())); 
    thread.start(); 
    a.exec(); 
    thread.wait(); 
} 

#include "main.moc" 
+2

Wow - đó là một câu trả lời khá toàn diện! –

+0

Ví dụ tuyệt vời phân loại các thùng chứa dùng chung của Qt khi kết hợp với 'QThread'. Thậm chí có một số C++ - 11 tình yêu như một tiền thưởng. Được thăng hạng. –

5

Khi giao tiếp bộ đệm lớn, đối tượng đệm mới() trong chuỗi sản xuất và khi được tải lên, xếp hàng/phát ra/bất kỳ bộ đệm * vào chuỗi tiêu dùng và ngay lập tức mới() một số khác, (vào cùng một bộ đệm var), cho tải tiếp theo của dữ liệu.

Sự cố: nếu luồng GUI không thể theo kịp, bạn sẽ bị mất bộ nhớ trừ khi bạn thực hiện một số biện pháp kiểm soát luồng (ví dụ: phân bổ trước một bộ đệm * và 'lưu hành' chúng).

Những gì tôi thường làm là phân bổ trước một số trường hợp bộ đệm trong một vòng lặp, (lên đến hàng nghìn trong một máy chủ lớn) và đẩy các trường hợp của chúng lên hàng đợi của nhà sản xuất người tiêu dùng. Nếu một chủ đề con muốn tải dữ liệu từ một số kết nối mạng vào một bộ đệm, nó phải bật một từ các hồ bơi và tải nó lên. Sau đó, nó có thể xếp hàng/phát ra/bất cứ bộ đệm nào vào chuỗi tiêu đề và bật bộ đệm khác cho bất kỳ dữ liệu nào có thể đến. Chuỗi tiêu thụ nhận được bộ điều khiển, xử lý dữ liệu và đẩy bộ đệm 'đã sử dụng' trở lại hàng đợi của nhóm tái sử dụng. Điều này cung cấp điều khiển luồng: nếu luồng con tải bộ đệm nhanh hơn chuỗi tiêu thụ có thể xử lý chúng, nó sẽ tìm thấy nhóm trống và chặn nó cho đến khi chuỗi tiêu đề trả về một số bộ đệm đã sử dụng, vì vậy hãy sử dụng bộ đệm/bộ nhớ. tránh liên tục mới/vứt bỏ, hoặc GC trong những ngôn ngữ hỗ trợ nó).

Tôi thích đổ số lượng hàng đợi vào thanh trạng thái GUI trên bộ hẹn giờ 1 giây - điều này cho phép tôi xem sử dụng bộ đệm (và nhanh chóng phát hiện nếu có rò rỉ :).

+0

Lợi thế quyết định trong việc phân bổ bộ nhớ bên trong chuỗi con và chuyển một con trỏ tới chính là gì, so với phân bổ trong chính và truyền con trỏ tới con khi tạo? – vsz

+1

Chủ đề con là nhà sản xuất dữ liệu - nó biết khi nào bộ đệm đầy và do đó khi xếp hàng con trỏ của nó và tạo/xóa một bộ đệm khác *. Chủ đề GUI, người tiêu dùng, không cần phải biết hoặc quản lý việc cấp phát bộ đệm con - nó có thể tự do xử lý bộ đệm khi chúng vào, biết rằng chuỗi con đã hoàn toàn từ bỏ việc sử dụng nó và bị nhàn rỗi hoặc lấp đầy thể hiện bộ đệm khác nhau. Miễn là trẻ ngay lập tức tạo/depools một thể hiện bộ đệm mới sau khi xếp hàng một lần, không có khả năng là hai luồng có thể truy cập cùng một cá thể bộ đệm. –

+2

Btw một cách dễ dàng để tránh bất kỳ cơ hội rò rỉ bộ nhớ nào là phát ra shared_ptr (hoặc nếu bạn thích Qt APIs, QSharedDataPointer) hơn là một con trỏ C++ nguyên. Bằng cách đó không có vấn đề gì xảy ra bạn biết mảng sẽ được giải phóng khi cả hai chủ đề không còn sử dụng nó nữa. –

Các vấn đề liên quan