久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

如何利用 Qt 使 QObject 方法線程安全?

How to leverage Qt to make a QObject method thread-safe?(如何利用 Qt 使 QObject 方法線程安全?)
本文介紹了如何利用 Qt 使 QObject 方法線程安全?的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧!

問題描述

假設(shè)我們在一個 QObject 派生類中編寫了一個非常量方法:

Suppose we wrote a non-const method in a QObject-deriving class:

class MyClass : public QObject {
  int x;
public:
  void method(int a) {
    x = a; // and possibly other things
  };
};

我們想讓該方法成為線程安全的:這意味著從任意線程調(diào)用它,并且從多個線程并發(fā)調(diào)用,不應(yīng)引入未定義的行為.

We want to make that method thread-safe: meaning that calling it from an arbitrary thread, and from multiple threads concurrently, shouldn't introduce undefined behavior.

  1. Qt 提供哪些機(jī)制/API 來幫助我們使該方法成為線程安全的?

  1. What mechanisms/APIs does Qt provide to help us make that method thread-safe?

Qt 的哪些機(jī)制/API 在該方法還可以完成其他事情"時可以使用?

What mechanisms/APIs from Qt one could use when the method does the "other things" too?

是否可以對其他事物"進(jìn)行分類,以告知要使用哪些 Qt 特定機(jī)制/API?

Is there any classification possible of the "other things" that would inform what Qt-specific mechanisms/APIs to use?

題外話是 C++ 標(biāo)準(zhǔn)本身提供的機(jī)制,以及確保線程安全的通用/非 Qt 特定方法.

Off topic are mechanisms provided by the C++ standard itself, and generic/non-Qt-specific ways of ensuring thread-safety.

推薦答案

適用的 Qt API 取決于線程安全方法的功能是什么.讓我們從最普遍到最具體的情況進(jìn)行介紹.

The applicable Qt APIs depend on what is the functionality of the thread-safe method. Let's cover the circumstances from the most general to most specific.

信號體由 moc 工具生成并且是線程安全的.

The bodies of signals are generated by the moc tool and are thread-safe.

推論 1:所有直接連接的槽/函子必須是線程安全的:否則破壞信號契約.雖然信號槽系統(tǒng)允許代碼解耦,但直接連接的特定情況會將信號的要求泄漏給連接的代碼!

Corollary 1: All directly-connected slots/functors must be thread-safe: doing otherwise breaks the contract of a signal. While the signal-slot system allows decoupling of code, the specific case of a direct connection leaks the requirements of a signal to the connected code!

推論 2:直接連接比自動連接更緊密.

最通用的方法是確保方法始終在對象的thread() 中執(zhí)行.這使得它在對象方面是線程安全的,但當(dāng)然,在方法內(nèi)使用任何其他對象也必須是線程安全的.

The most general approach is that of ensuring that the method's is always executed in the object's thread(). This makes it thread-safe in respect to the object, but of course the use of any other objects from within the method must be done thread-safely too.

一般來說,線程不安全的方法只能從對象的thread()中調(diào)用:

In general, a thread-unsafe method can only be called from the object's thread():

void MyObject::method() {
  Q_ASSERT(thread() == QThread::currentThread());
  ...
}

無線程對象的特殊情況需要注意.當(dāng)一個對象的線程結(jié)束時,它變成無線程的.然而,僅僅因為對象是無線程的并不能使其所有方法都是線程安全的.出于線程安全的目的,最好選擇一個線程來擁有"此類對象.這樣的線程可能是主線程:

The special case of a thread-less object requires some care. An object becomes thread-less when its thread finishes. Yet, just because the object is thread-less doesn't make all of its methods thread-safe. It would be preferable to choose one thread to "own" such objects for the purpose of thread-safety. Such thread might be the main thread:

Q_ASSERT(QThread::currentThread() == (thread() ? thread() : qApp()->thread()));

我們的工作就是實現(xiàn)這一主張.方法如下:

Our job is to fulfill that assertion. Here's how:

  1. 利用線程安全信號.

  1. Leverage thread-safe signals.

由于信號是線程安全的,我們可以使我們的方法成為信號,并將其實現(xiàn)托管在插槽中:

Since signals are thread-safe, we could make our method a signal, and host its implementation in a slot:

class MyObject : public QObject {
  Q_OBJECT
  int x;
  void method_impl(int a) {
    x = a;
  }
  Q_SIGNAL void method_signal(int);
public:
  void method(int a) { method_signal(a); }
  MyObject(QObject * parent = nullptr) : QObject{parent} {
    connect(this, &MyObject::method, this, &MyObject::method_impl);
  }
};

這種方法可以支持?jǐn)嘌裕苋唛L,并且每個參數(shù)都執(zhí)行額外的動態(tài)分配(至少從 Qt 5.7 開始).

This approach works to uphold the assertion, but is verbose and performs an additional dynamic allocation per each argument (as of Qt 5.7 at least).

將函子中的調(diào)用分派給對象的線程.

Dispatch the call in a functor to the object's thread.

有很多方法;讓我們展示一個執(zhí)行最少動態(tài)分配的方法:在大多數(shù)情況下,只有一個.

There are many ways of doing it; let's present one that does the minimum number of dynamic allocations: in most cases, exactly one.

我們可以將方法的調(diào)用包裝在一個函子中,并確保它以線程安全的方式執(zhí)行:

We can wrap the call of the method in a functor and ensure that it's executed thread-safely:

void method1(int val) {
   if (!isSafe(this))
      return postCall(this, [=]{ method1(val); });
   qDebug() << __FUNCTION__;
   num = val;
}

如果當(dāng)前線程是對象的線程,則沒有開銷,也沒有數(shù)據(jù)復(fù)制.否則,調(diào)用將推遲到對象線程中的事件循環(huán),如果對象是無線程的,則調(diào)用將推遲到主事件循環(huán).

There is no overhead and no copying of data if the current thread is the object's thread. Otherwise, the call will be deferred to the event loop in the object's thread, or to the main event loop if the object is threadless.

bool isSafe(QObject * obj) {
   Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
   auto thread = obj->thread() ? obj->thread() : qApp->thread();
   return thread == QThread::currentThread();
}

template <typename Fun> void postCall(QObject * obj, Fun && fun) {
   qDebug() << __FUNCTION__;
   struct Event : public QEvent {
      using F = typename std::decay<Fun>::type;
      F fun;
      Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
      Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
      ~Event() { fun(); }
   };
   QCoreApplication::postEvent(
            obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

  • 將調(diào)用分派到對象的線程.

  • Dispatch the call to the object's thread.

    這是上述的變體,但沒有使用函子.postCall 函數(shù)可以顯式包裝參數(shù):

    This is a variation on the above, but without using a functor. The postCall function can wrap the parameters explicitly:

    void method2(const QString &val) {
       if (!isSafe(this))
          return postCall(this, &Class::method2, val);
       qDebug() << __FUNCTION__;
       str = val;
    }
    

    那么:

    template <typename Class, typename... Args>
    struct CallEvent : public QEvent {
       // See https://stackoverflow.com/a/7858971/1329652
       // See also https://stackoverflow.com/a/15338881/1329652
       template <int ...> struct seq {};
       template <int N, int... S> struct gens { using type = typename gens<N-1, N-1, S...>::type; };
       template <int ...S>        struct gens<0, S...> { using type = seq<S...>; };
       template <int ...S>        void callFunc(seq<S...>) { (obj->*method)(std::get<S>(args)...); }
       Class * obj;
       void (Class::*method)(Args...);
       std::tuple<typename std::decay<Args>::type...> args;
       CallEvent(Class * obj, void (Class::*method)(Args...), Args&&... args) :
          QEvent(QEvent::None), obj(obj), method(method), args(std::move<Args>(args)...) {}
       ~CallEvent() { callFunc(typename gens<sizeof...(Args)>::type()); }
    };
    
    template <typename Class, typename... Args> void postCall(Class * obj, void (Class::*method)(Args...), Args&& ...args) {
       qDebug() << __FUNCTION__;
       QCoreApplication::postEvent(
                obj->thread() ? static_cast<QObject*>(obj) : qApp, new CallEvent<Class, Args...>{obj, method, std::forward<Args>(args)...});
    }
    

  • 保護(hù)對象的數(shù)據(jù)

    如果該方法對一組成員進(jìn)行操作,則可以使用互斥鎖來序列化對這些成員的訪問.利用 QMutexLocker 表達(dá)您的意圖,并通過構(gòu)造避免未發(fā)布的互斥鎖錯誤.

    Protecting the Object's Data

    If the method operates on a set of members, the access to these members can be serialized by using a mutex. Leverage QMutexLocker to express your intent and avoid unreleased mutex errors by construction.

    class MyClass : public QObject {
      Q_OBJECT
      QMutex m_mutex;
      int m_a;
      int m_b;
    public:
      void method(int a, int b) {
        QMutexLocker lock{&m_mutex};
        m_a = a;
        m_b = b;
      };
    };
    

    在使用特定于對象的互斥鎖和在對象線程中調(diào)用方法體之間的選擇取決于應(yīng)用程序的需要.如果方法中訪問的所有成員都是私有的,那么使用互斥鎖是有意義的,因為我們處于控制之中,并且可以通過設(shè)計確保所有訪問都受到保護(hù).使用特定于對象的互斥鎖也將方法與對象事件循環(huán)上的爭用分離開來——因此可能具有性能優(yōu)勢.另一方面,如果方法必須訪問它不擁有的對象上的線程不安全的方法,那么互斥鎖就不夠用了,方法的主體應(yīng)該在對象的線程中執(zhí)行.

    The choice between using an object-specific mutex and invoking the body of the method in the object's thread depends on the needs of the application. If all of the members accessed in the method are private then using a mutex makes sense since we're in control and can ensure, by design, that all access is protected. The use of object-specific mutex also decouples the method from the contention on the object's event loop - so might have performance benefits. On the other hand, is the method must access thread-unsafe methods on objects it doesn't own, then a mutex would be insufficient, and the method's body should be executed in the object's thread.

    如果 const 方法讀取可以包裝在 QAtomicIntegerQAtomicPointer 中的單個數(shù)據(jù),我們可以使用原子字段:

    If the const method reads a single piece of data that can be wrapped in a QAtomicInteger or QAtomicPointer, we can use an atomic field:

    class MyClass : public QObject {
      QAtomicInteger<int> x;
    public:
      /// Thread-Safe
      int method() const {
        return x.load();
      };
    };
    

    修改簡單的成員變量

    如果該方法修改了可以包裝在QAtomicIntegerQAtomicPointer 中的單個數(shù)據(jù),則可以使用原子原語,我們可以使用原子字段:

    Modifying a Simple Member Variable

    If the method modifies a single piece of data that can be wrapped in QAtomicInteger or QAtomicPointer, and the operation can be done using an atomic primitive, we can use an atomic field:

    class MyClass : public QObject {
      QAtomicInteger<int> x;
    public:
      /// Thread-Safe
      void method(int a) {
        x.fetchAndStoreOrdered(a);
      };
    };
    

    這種方法一般不會擴(kuò)展到修改多個成員:某些成員更改而其他成員不更改的中間狀態(tài)將對其他線程可見.通常這會破壞其他代碼所依賴的不變量.

    This approach doesn't extend to modifying multiple members in general: the intermediate states where some members are changed and some other are not will be visible to other threads. Usually this would break invariants that other code depends on.

    這篇關(guān)于如何利用 Qt 使 QObject 方法線程安全?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!

    【網(wǎng)站聲明】本站部分內(nèi)容來源于互聯(lián)網(wǎng),旨在幫助大家更快的解決問題,如果有圖片或者內(nèi)容侵犯了您的權(quán)益,請聯(lián)系我們刪除處理,感謝您的支持!

    相關(guān)文檔推薦

    How can I read and manipulate CSV file data in C++?(如何在 C++ 中讀取和操作 CSV 文件數(shù)據(jù)?)
    In C++ why can#39;t I write a for() loop like this: for( int i = 1, double i2 = 0; (在 C++ 中,為什么我不能像這樣編寫 for() 循環(huán): for( int i = 1, double i2 = 0;)
    How does OpenMP handle nested loops?(OpenMP 如何處理嵌套循環(huán)?)
    Reusing thread in loop c++(在循環(huán) C++ 中重用線程)
    Precise thread sleep needed. Max 1ms error(需要精確的線程睡眠.最大 1ms 誤差)
    Is there ever a need for a quot;do {...} while ( )quot; loop?(是否需要“do {...} while ()?環(huán)形?)
    主站蜘蛛池模板: 国产一区二区自拍 | 操久久 | 婷婷久久久久 | 久久久久久久久一区 | 一区二区三区在线 | 欧 | 国产这里只有精品 | 欧美天堂一区 | 亚洲一区二区日韩 | 国产精品99久久久久久久vr | 一区二区三区四区免费观看 | 欧美成人h版在线观看 | 亚洲三级免费看 | 亚洲精品乱码久久久久久黑人 | 可以看黄的视频 | 精品久久久久久久人人人人传媒 | 久久久网| 精品国产欧美一区二区三区成人 | 中文字幕一区二区视频 | 男人的天堂视频网站 | 国产伦精品一区二区三区高清 | 秋霞电影一区二区三区 | 久久一 | 中国xxxx性xxxx产国 | 欧美精品一区在线 | 久久久国产一区 | 水蜜桃久久夜色精品一区 | 中文字幕日韩一区 | 一区二区三区国产好 | 久久久蜜臀国产一区二区 | 国产精品综合一区二区 | 日韩欧美在线观看 | 亚洲欧美一区二区三区视频 | 国产精品久久久久久久久免费软件 | www.日本在线 | 99精品国产一区二区三区 | 国产一区二区精品在线观看 | 成人福利片 | 成人欧美一区二区三区黑人孕妇 | 成人在线观看免费 | 国产精品日韩欧美一区二区三区 | 亚洲aⅴ精品 |