<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                #(75):線程總結 前面我們已經詳細介紹過有關線程的一些值得注意的事項。現在我們開始對線程做一些總結。 有關線程,你可以做的是: * 在`QThread`子類添加信號。這是絕對安全的,并且也是正確的(前面我們已經詳細介紹過,發送者的線程依附性沒有關系) 不應該做的是: * 調用`moveToThread(this)`函數 * 指定連接類型:這通常意味著你正在做錯誤的事情,比如將`QThread`控制接口與業務邏輯混雜在了一起(而這應該放在該線程的一個獨立對象中) * 在`QThread`子類添加槽函數:這意味著它們將在錯誤的線程被調用,也就是`QThread`對象所在線程,而不是`QThread`對象管理的線程。這又需要你指定連接類型或者調用`moveToThread(this)`函數 * 使用`QThread::terminate()`函數 不能做的是: * 在線程還在運行時退出程序。使用`QThread::wait()`函數等待線程結束 * 在`QThread`對象所管理的線程仍在運行時就銷毀該對象。如果你需要某種“自行銷毀”的操作,你可以把`finished()`信號同`deleteLater()`槽連接起來 那么,下面一個問題是:我什么時候應該使用線程? **首先,當你不得不使用同步 API 的時候。** 如果你需要使用一個沒有非阻塞 API 的庫或代碼(所謂非阻塞 API,很大程度上就是指信號槽、事件、回調等),那么,避免事件循環被阻塞的解決方案就是使用進程或者線程。不過,由于開啟一個新的工作進程,讓這個進程去完成任務,然后再與當前進程進行通信,這一系列操作的代價都要比開啟線程要昂貴得多,所以,線程通常是最好的選擇。 一個很好的例子是地址解析服務。注意我們這里并不討論任何第三方 API,僅僅假設一個有這樣功能的庫。這個庫的工作是將一個主機名轉換成地址。這個過程需要去到一個系統(也就是域名系統,Domain Name System, DNS)執行查詢,這個系統通常是一個遠程系統。一般這種響應應該瞬間完成,但是并不排除遠程服務器失敗、某些包可能會丟失、網絡可能失去鏈接等等。簡單來說,我們的查詢可能會等幾十秒鐘。 UNIX 系統上的標準 API 是阻塞的(不僅是舊的`gethostbyname(3)`,就連新的`getservbyname(3)`和`getaddrinfo(3)`也是一樣)。Qt 提供的`QHostInfo`類同樣用于地址解析,默認情況下,內部使用一個`QThreadPool`提供后臺運行方式的查詢(如果關閉了 Qt 的線程支持,則提供阻塞式 API)。 另外一個例子是圖像加載和縮放。`QImageReader`和`QImage`只提供了阻塞式 API,允許我們從設備讀取圖片,或者是縮放到不同的分辨率。如果你需要處理很大的圖像,這種任務會花費幾十秒鐘。 **其次,當你希望擴展到多核應用的時候。** 線程允許你的程序利用多核系統的優勢。每一個線程都可以被操作系統獨立調度,如果你的程序運行在多核機器上,調度器很可能會將每一個線程分配到各自的處理器上面運行。 舉個例子,一個程序需要為很多圖像生成縮略圖。一個具有固定 n 個線程的線程池,每一個線程交給系統中的一個可用的 CPU 進行處理(我們可以使用`QThread::idealThreadCount()`獲取可用的 CPU 數)。這樣的調度將會把圖像縮放工作交給所有線程執行,從而有效地提升效率,幾乎達到與 CPU 數的線性提升(實際情況不會這么簡單,因為有時候 CPU 并不是瓶頸所在)。 **第三,當你不想被別人阻塞的時候。** 這是一個相當高級的話題,所以你現在可以暫時不看這段。這個問題的一個很好的例子是在 WebKit 中使用`QNetworkAccessManager`。WebKit 是一個現代的瀏覽器引擎。它幫助我們展示網頁。Qt 中的`QWebView`就是使用的 WebKit。 `QNetworkAccessManager`則是 Qt 處理 HTTP 請求和響應的通用類。我們可以將它看做瀏覽器的網絡引擎。在 Qt 4.8 之前,這個類沒有使用任何協助工作線程,所有的網絡處理都是在`QNetworkAccessManager`及其`QNetworkReply`所在線程完成。 雖然在網絡處理中不使用線程是一個好主意,但它也有一個很大的缺點:如果你不能及時從 socket 讀取數據,內核緩沖區將會被填滿,于是開始丟包,傳輸速度將會直線下降。 socket 活動(也就是從一個 socket 讀取一些可用的數據)是由 Qt 的事件循環管理的。因此,阻塞事件循環將會導致傳輸性能的損失,因為沒有人會獲得有數據可讀的通知,因此也就沒有人能夠讀取這些數據。 但是什么會阻塞事件循環?最壞的答案是:WebKit 自己!只要收到數據,WebKit 就開始生成網頁布局。不幸的是,這個布局的過程非常復雜和耗時,因此它會阻塞事件循環。盡管阻塞時間很短,但是足以影響到正常的數據傳輸(寬帶連接在這里發揮了作用,在很短時間內就可以塞滿內核緩沖區)。 總結一下上面所說的內容: * WebKit 發起一次請求 * 從服務器響應獲取一些數據 * WebKit 利用到達的數據開始進行網頁布局,阻塞事件循環 * 由于事件循環被阻塞,也就沒有了可用的事件循環,于是操作系統接收了到達的數據,但是卻不能從`QNetworkAccessManager`的 socket 讀取 * 內核緩沖區被填滿,傳輸速度變慢 網頁的整體加載時間被自身的傳輸速度的降低而變得越來越壞。 注意,由于`QNetworkAccessManager`和`QNetworkReply`都是`QObject`,所以它們都不是線程安全的,因此你不能將它們移動到另外的線程繼續使用。因為它們可能同時有兩個線程訪問:你自己的和它們所在的線程,這是因為派發給它們的事件會由后面一個線程的事件循環發出,但你不能確定哪一線程是“后面一個”。 Qt 4.8 之后,`QNetworkAccessManager`默認會在一個獨立的線程處理 HTTP 請求,所以導致 GUI 失去響應以及操作系統緩沖區過快填滿的問題應該已經被解決了。 那么,什么情況下不應該使用線程呢? **定時器** 這可能是最容易誤用線程的情況了。如果我們需要每隔一段時間調用一個函數,很多人可能會這么寫代碼: ~~~ // 最錯誤的代碼 while (condition) { doWork(); sleep(1); // C 庫里面的 sleep(3) 函數 } ~~~ 當讀過我們前面的文章之后,可能又會引入線程,改成這樣的代碼: ~~~ // 錯誤的代碼 class Thread : public QThread { protected: void run() { while (condition) { // 注意,如果我們要在別的線程修改 condition,那么它也需要加鎖 doWork(); sleep(1); // 這次是 QThread::sleep() } } }; ~~~ 最好最簡單的實現是使用定時器,比如`QTimer`,設置 1s 超時,然后將`doWork()`作為槽: ~~~ class Worker : public QObject { Q_OBJECT public: Worker() { connect(&timer, SIGNAL(timeout()), this, SLOT(doWork())); timer.start(1000); } private slots: void doWork() { /* ... */ } private: QTimer timer; }; ~~~ **網絡/狀態機** 下面是一個很常見的處理網絡操作的設計模式: ~~~ socket->connect(host); socket->waitForConnected(); data = getData(); socket->write(data); socket->waitForBytesWritten(); socket->waitForReadyRead(); socket->read(response); reply = process(response); socket->write(reply); socket->waitForBytesWritten(); /* ... */ ~~~ 在經過前面幾章的介紹之后,不用多說,我們就會發現這里的問題:大量的`waitFor*()`函數會阻塞事件循環,凍結 UI 界面等等。注意,上面的代碼還沒有加入異常處理,否則的話肯定會更復雜。這段代碼的錯誤在于,我們的網絡實際是異步的,如果我們非得按照同步方式處理,就像拿起槍打自己的腳。為了解決這個問題,很多人會簡單地將這段代碼移動到一個新的線程。 一個更抽象的例子是: ~~~ result = process_one_thing(); if (result->something()) { process_this(); } else { process_that(); } wait_for_user_input(); input = read_user_input(); process_user_input(input); /* ... */ ~~~ 這段抽象的代碼與前面網絡的例子有“異曲同工之妙”。 讓我們回過頭來看看這段代碼究竟是做了什么:我們實際是想創建一個狀態機,這個狀態機要根據用戶的輸入作出合理的響應。例如我們網絡的例子,我們實際是想要構建這樣的東西: ~~~ 空閑 → 正在連接(調用<code>connectToHost()</code>) 正在連接 → 成功連接(發出<code>connected()</code>信號) 成功連接 → 發送登錄數據(將登錄數據發送到服務器) 發送登錄數據 → 登錄成功(服務器返回 ACK) 發送登錄數據 → 登錄失敗(服務器返回 NACK) ~~~ 以此類推。 既然知道我們的實際目的,我們就可以修改代碼來創建一個真正的狀態機(Qt 甚至提供了一個狀態機類:`QStateMachine`)。創建狀態機最簡單的方法是使用一個枚舉來記住當前狀態。我們可以編寫如下代碼: ~~~ class Object : public QObject { Q_OBJECT enum State { State1, State2, State3 /* ... */ }; State state; public: Object() : state(State1) { connect(source, SIGNAL(ready()), this, SLOT(doWork())); } private slots: void doWork() { switch (state) { case State1: /* ... */ state = State2; break; case State2: /* ... */ state = State3; break; /* ... */ } } }; ~~~ `source`對象是哪來的?這個對象其實就是我們關心的對象:例如,在網絡的例子中,我們可能希望把 socket 的`QAbstractSocket::connected()`或者`QIODevice::readyRead()`信號與我們的槽函數連接起來。當然,我們很容易添加更多更合適的代碼(比如錯誤處理,使用`QAbstractSocket::error()`信號就可以了)。這種代碼是真正異步、信號驅動的設計。 **將任務分割成若干部分** 假設我們有一個很耗時的計算,我們不能簡單地將它移動到另外的線程(或者是我們根本無法移動它,比如這個任務必須在 GUI 線程完成)。如果我們將這個計算任務分割成小塊,那么我們就可以及時返回事件循環,從而讓事件循環繼續派發事件,調用處理下一個小塊的函數。回一下如何實現隊列連接,我們就可以輕松完成這個任務:將事件提交到接收對象所在線程的事件循環;當事件發出時,響應函數就會被調用。 我們可以使用`QMetaObject::invokeMethod()`函數,通過指定`Qt::QueuedConnection`作為調用類型來達到相同的效果。不過這要求函數必須是內省的,也就是說這個函數要么是一個槽函數,要么標記有`Q_INVOKABLE`宏。如果我們還需要傳遞參數,我們需要使用`qRegisterMetaType()`函數將參數注冊到 Qt 元類型系統。下面是代碼示例: ~~~ class Worker : public QObject { Q_OBJECT public slots: void startProcessing() { processItem(0); } void processItem(int index) { /* 處理 items[index] ... */ if (index < numberOfItems) { QMetaObject::invokeMethod(this, "processItem", Qt::QueuedConnection, Q_ARG(int, index + 1)); } } }; ~~~ 由于沒有任何線程調用,所以我們可以輕易對這種計算任務執行暫停/恢復/取消,以及獲取結果。 至此,我們利用五個章節將有關線程的問題簡單介紹了下。線程應該說是全部設計里面最復雜的部分之一,所以這部分內容也會比較困難。在實際運用中肯定會更多的問題,這就只能讓我們具體分析了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看