2012-07-01 16 views
17

के बीच बड़ी मात्रा में डेटा भेजना मेरे पास QThread है जो नियमित रूप से डेटा की काफी बड़ी मात्रा (प्रति सेकंड मेगाबाइट्स) उत्पन्न करता है, और इसे इसे पैरेंट (जीयूआई) थ्रेड में प्रेषित करने की आवश्यकता होती है।क्यूटी धागे

मुझे डर है कि मैं QThread की आंतरिक कार्यप्रणाली में निश्चित नहीं हूं इसलिए मैं एक सर्वोत्तम अभ्यास के लिए पूछना चाहता हूं।

स्पष्ट रूप से, डेटा संचारित करने का सबसे सीधा तरीका केवल emit एक सरणी है। हालांकि, यह कितना कुशल है? क्या क्यूटी को पता है कि इसका उपयोग कहां किया जाता है और इसे भेजने और प्राप्त करने पर गहरी प्रतिलिपि से बचा जाता है?

यदि नहीं, तो मैं खुशी सिर्फ स्मृति मुख्य थ्रेड में आवंटित और बच्चे धागा जहां यह डेटा (और प्रगति के बारे में केवल emit लघु संदेश) लिखेंगे के लिए सूचक दे ​​सकते हैं। यह मेरे लिए सबसे सुंदर समाधान प्रतीत नहीं होता है, यही कारण है कि मैं पूछ रहा हूं।

यदि क्यूटी उत्सर्जन और प्राप्त करते समय एकाधिक बफर में डेटा की प्रतिलिपि से बचाता है, तो क्या यह सभी प्रणालियों में गारंटीकृत है? मेरे पास विभिन्न ओएस के तहत बेंचमार्किंग करने का प्रयास करने के लिए संसाधन नहीं हैं।

+0

हम और अधिक जानना चाहते हैं। क्या मुख्य धागे डेटा खोने के लिए स्वीकार्य है? डेटा के साथ मुख्य धागा क्या करता है, वैसे भी? लेकिन जो कुछ भी आपकी ज़रूरत है, मैं विश्वास नहीं कर सकता कि एक सरणी उत्सर्जित करना इष्टतम समाधान है। – TonyK

उत्तर

32

QThread की आंतरिक कार्यप्रणाली अप्रासंगिक हैं: वे ईवेंट लूप कैसे काम करते हैं में कोई भूमिका निभाते हैं। जब आप emitQObject में एक सिग्नल जो स्लॉट के ऑब्जेक्ट से भिन्न धागे में रहता है, तो सिग्नल को QMetaCallEvent के रूप में प्राप्त थ्रेड के ईवेंट कतार में पोस्ट किया जाएगा। प्राप्त करने वाले थ्रेड में चल रहे ईवेंट लूप इस घटना पर कार्य करेंगे और कॉल को उस स्लॉट में निष्पादित करेंगे जो उत्सर्जित सिग्नल से जुड़ा था।

तो, कोई फर्क नहीं पड़ता कि क्या होता है, सिग्नल के माध्यम से जो भी डेटा आप भेजते हैं, वह अंततः QEvent-derived क्लास के उदाहरण में एक पेलोड के रूप में समाप्त हो जाएगा।

मुद्दे का मांस जब QMetaCallEvent घटना पाश तक पहुँच जाता है और कंटेनर एक तर्क के रूप स्लॉट में पारित कर दिया जाता है। बेशक प्रतिलिपि बनाने वालों को रास्ते में कई बार बुलाया जा सकता है।नीचे कुछ सरल कोड है कि कितनी बार प्रति निर्माता और डिफ़ॉल्ट निर्माता को दर्शाता है वास्तव में,

  • एक अनुमानित साझा कॉपी-ऑन-राइट कंटेनर के डेटा के सदस्यों (QVector) के तत्वों पर

    कहा जाता है है

  • एक कंटेनर के लिए खड़े कस्टम वर्ग पर

आप सुखद आश्चर्यचकित हो जाएगा :)

के बाद से क्यूटी कंटेनरों अनुमानित साझा कर रहे हैं प्रति-ऑन-राइट, उनके प्रति निर्माण नगण्य लागत है: यह सब किया है एक संदर्भ काउंटर निर्माण पर atomically वृद्धि की जाती है है । उदाहरण के लिए, डेटा सदस्यों में से कोई भी कॉपी नहीं किया गया है।

अलास, प्री -11 सी ++ अपनी बदसूरत तरफ दिखाता है: यदि स्लॉट कोड किसी भी तरह से कंटेनर को संशोधित करता है, तो स्लॉट के संदर्भों को इस तरह से पास करने का कोई तरीका नहीं है जिससे संकलक को पता चल सके कि मूल कंटेनर है अब और जरूरत नहीं है। इस प्रकार: यदि स्लॉट कंटेनर के लिए एक संदर्भ संदर्भ प्राप्त करता है, तो आप गारंटी देते हैं कि कोई प्रतियां नहीं की जाएंगी। यदि स्लॉट को कंटेनर और की एक लिखने योग्य प्रति प्राप्त होती है, तो आप इसे संशोधित करते हैं, कॉल साइट पर जिंदा उदाहरण की आवश्यकता नहीं होने के बाद से पूरी तरह से अनावश्यक प्रतिलिपि बनाई जाएगी। सी ++ में - 11 आप एक पैरामीटर संदर्भ पैरामीटर के रूप में पारित करेंगे। फ़ंक्शन कॉल में एक रावल्यू संदर्भ पास करना कॉलर में पास ऑब्जेक्ट के जीवनकाल को समाप्त करता है।

नमूना कोड उत्पादन:

"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

वाह - यह एक बहुत व्यापक जवाब है! –

+0

क्यूटी के साझा कंटेनर को 'क्यूटीएचडी' के साथ जोड़कर महान उदाहरण सॉर्ट करना। बोनस के रूप में कुछ सी ++ - 11 प्यार भी मिला। Upvoted। –

5

बड़े बफरों को संप्रेषित करते समय, यह निर्माता थ्रेड में नए() बफर ऑब्जेक्ट्स के लिए 'पारंपरिक' होता है, और जब लोड हो जाता है, कतार/उत्सर्जित/जो भी * उपभोक्ता थ्रेड के लिए बफर और तुरंत नया() दूसरा होता है, (डेटा के अगले लोड के लिए, वही * बफर var) में।

समस्या: यदि आपका जीयूआई थ्रेड नहीं रख सकता है, तो आप मेमोरी रनवे प्राप्त करेंगे जबतक कि आप कुछ प्रवाह-नियंत्रण उपाय नहीं लेते हैं (उदाहरण के लिए * बफर के पूल को आवंटित करना और 'उन्हें प्रसारित करना)।

मैं आमतौर पर जो करता हूं वह लूप में कुछ बफर उदाहरणों को आवंटित करता है, (एक बड़े सर्वर में हजारों तक), और अपने उदाहरण को निर्माता-उपभोक्ता 'पूल कतार' पर दबाएं। यदि कोई बच्चा थ्रेड कुछ नेटवर्क कनेक्शन से डेटा को बफर में लोड करना चाहता है, तो उसे पूल से एक पॉप करना होगा और इसे लोड करना होगा। इसके बाद यह किसी भी डेटा के लिए बफर को क्यूई/उत्सर्जित कर सकता है और जो भी डेटा हो सकता है, उसके लिए एक और बफर पॉप कर सकता है। उपभोक्ता धागा बफर हो जाता है, डेटा को संसाधित करता है और 'इस्तेमाल' बफर को पूल कतार पर वापस धक्का देता है फिर से उपयोग। यह प्रवाह-नियंत्रण प्रदान करता है: यदि बच्चे धागा उपभोक्ता थ्रेड की तुलना में तेजी से बफर लोड करता है, तो उसे पूल खाली कर दिया जाएगा और जब तक उपभोक्ता थ्रेड कुछ उपयोग किए गए बफर वापस नहीं कर लेता है, तो बफर/मेमोरी उपयोग को कैप करना, (और यह भी निरंतर नए/निपटान से बचें, या उन भाषाओं में जीसी जो इसका समर्थन करते हैं)।

मुझे 1-सेकंड टाइमर पर पूल कतार गणना को जीयूआई स्टेटस बार में डंप करना पसंद है - इससे मुझे बफर उपयोग देखने की अनुमति मिलती है, (और किसी भी रिसाव के तुरंत बाद स्पॉट करें)।

+0

बच्चे के धागे के अंदर स्मृति आवंटित करने और मुख्य में एक सूचक को गुजरने में निर्णायक लाभ क्या है, मुख्य रूप से आवंटित बनाम और सृजन पर बच्चे को पॉइंटर पास करना? – vsz

+1

बच्चा धागा डेटा निर्माता है - यह जानता है कि जब एक बफर भरा होता है और इसलिए जब उसके पॉइंटर को कतारबद्ध किया जाता है और एक और बफर * बना/हटा देता है। जीयूआई थ्रेड, उपभोक्ता को बाल थ्रेड बफर आवंटन को जानने या प्रबंधित करने की आवश्यकता नहीं है - यह बफर को स्वतंत्र रूप से संसाधित कर सकता है क्योंकि यह पता चलता है कि बाल धागे ने इसका उपयोग पूरी तरह से त्याग दिया है और या तो निष्क्रिय है या भर रहा है अलग बफर उदाहरण। जब तक बच्चा तुरंत बंद होने के बाद एक नया बफर इंस्टेंस बनाता/हटा देता है, तब तक कोई खतरा नहीं होता है कि दो थ्रेड कभी भी उसी बफर इंस्टेंस तक पहुंच सकते हैं। –

+2

बीटीडब्ल्यू मेमोरी रिसाव के किसी भी मौके से बचने का एक आसान तरीका है एक साझा सी ++ पॉइंटर के बजाय एक shared_ptr (या यदि आप क्यूटी एपीआई, एक QSharedDataPointer) पसंद करते हैं। इस तरह से कोई फर्क नहीं पड़ता कि आप क्या जानते हैं कि सरणी मुक्त हो जाएगी जब दोनों धागे इसका उपयोग नहीं कर रहे हैं। –