pbootcms网站模板|日韩1区2区|织梦模板||网站源码|日韩1区2区|jquery建站特效-html5模板网

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

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

問題描述

假設我們在一個 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
  };
};

我們想讓該方法成為線程安全的:這意味著從任意線程調用它,并且從多個線程并發調用,不應引入未定義的行為.

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 提供哪些機制/API 來幫助我們使該方法成為線程安全的?

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

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

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

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

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

題外話是 C++ 標準本身提供的機制,以及確保線程安全的通用/非 Qt 特定方法.

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

推薦答案

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

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:所有直接連接的槽/函子必須是線程安全的:否則破壞信號契約.雖然信號槽系統允許代碼解耦,但直接連接的特定情況會將信號的要求泄漏給連接的代碼!

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() 中執行.這使得它在對象方面是線程安全的,但當然,在方法內使用任何其他對象也必須是線程安全的.

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()中調用:

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

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

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

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()));

我們的工作就是實現這一主張.方法如下:

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

  1. 利用線程安全信號.

  1. Leverage thread-safe signals.

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

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);
  }
};

這種方法可以支持斷言,但很冗長,并且每個參數都執行額外的動態分配(至少從 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).

將函子中的調用分派給對象的線程.

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

有很多方法;讓我們展示一個執行最少動態分配的方法:在大多數情況下,只有一個.

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

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

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;
}

如果當前線程是對象的線程,則沒有開銷,也沒有數據復制.否則,調用將推遲到對象線程中的事件循環,如果對象是無線程的,則調用將推遲到主事件循環.

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)));
}

  • 將調用分派到對象的線程.

  • Dispatch the call to the object's thread.

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

    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)...});
    }
    

  • 保護對象的數據

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

    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;
      };
    };
    

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

    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 中的單個數據,我們可以使用原子字段:

    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 中的單個數據,則可以使用原子原語,我們可以使用原子字段:

    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);
      };
    };
    

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

    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.

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

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

    相關文檔推薦

    How can I read and manipulate CSV file data in C++?(如何在 C++ 中讀取和操作 CSV 文件數據?)
    In C++ why can#39;t I write a for() loop like this: for( int i = 1, double i2 = 0; (在 C++ 中,為什么我不能像這樣編寫 for() 循環: for( int i = 1, double i2 = 0;)
    How does OpenMP handle nested loops?(OpenMP 如何處理嵌套循環?)
    Reusing thread in loop c++(在循環 C++ 中重用線程)
    Precise thread sleep needed. Max 1ms error(需要精確的線程睡眠.最大 1ms 誤差)
    Is there ever a need for a quot;do {...} while ( )quot; loop?(是否需要“do {...} while ()?環形?)
    主站蜘蛛池模板: 不锈钢管件(不锈钢弯头,不锈钢三通,不锈钢大小头),不锈钢法兰「厂家」-浙江志通管阀 | 民用音响-拉杆音响-家用音响-ktv专用音响-万昌科技 | 百度爱采购运营研究社社群-店铺托管-爱采购代运营-良言多米网络公司 | 报警器_家用防盗报警器_烟雾报警器_燃气报警器_防盗报警系统厂家-深圳市刻锐智能科技有限公司 | BAUER减速机|ROSSI-MERSEN熔断器-APTECH调压阀-上海爱泽工业设备有限公司 | 磁力反应釜,高压釜,实验室反应釜,高温高压反应釜-威海自控反应釜有限公司 | 无负压供水设备,消防稳压供水设备-淄博创辉供水设备有限公司 | 桥架-槽式电缆桥架-镀锌桥架-托盘式桥架 - 上海亮族电缆桥架制造有限公司 | 世纪豪门官网 世纪豪门集成吊顶加盟电话 世纪豪门售后电话 | 河南不锈钢水箱_地埋水箱_镀锌板水箱_消防水箱厂家-河南联固供水设备有限公司 | 炒货机-炒菜机-炒酱机-炒米机@霍氏机械 | atcc网站,sigma试剂价格,肿瘤细胞现货,人结肠癌细胞株购买-南京科佰生物 | 欧版反击式破碎机-欧版反击破-矿山石料破碎生产线-青州奥凯诺机械 | 电动车头盔厂家_赠品头盔_安全帽批发_山东摩托车头盔—临沂承福头盔 | 气胀轴|气涨轴|安全夹头|安全卡盘|伺服纠偏系统厂家-天机传动 | 广州监控安装公司_远程监控_安防弱电工程_无线wifi覆盖_泉威安防科技 | 济南玻璃安装_济南玻璃门_济南感应门_济南玻璃隔断_济南玻璃门维修_济南镜片安装_济南肯德基门_济南高隔间-济南凯轩鹏宇玻璃有限公司 | 大立教育官网-一级建造师培训-二级建造师培训-造价工程师-安全工程师-监理工程师考试培训 | 细砂提取机,隔膜板框泥浆污泥压滤机,螺旋洗砂机设备,轮式洗砂机械,机制砂,圆锥颚式反击式破碎机,振动筛,滚筒筛,喂料机- 上海重睿环保设备有限公司 | 西门子伺服控制器维修-伺服驱动放大器-828D数控机床维修-上海涌迪 | 空心明胶胶囊|植物胶囊|清真胶囊|浙江绿键胶囊有限公司欢迎您! | 展厅装修公司|企业展厅设计|展厅制作|展厅搭建—广州展厅装饰公司 | 槽钢冲孔机,槽钢三面冲,带钢冲孔机-山东兴田阳光智能装备股份有限公司 | 集菌仪_智能集菌仪_全封闭集菌仪_无菌检查集菌仪厂家-那艾 | 烘箱-工业烘箱-工业电炉-实验室干燥箱 - 苏州华洁烘箱制造有限公司 | 猎头招聘_深圳猎头公司_知名猎头公司| 电解抛光加工_不锈钢电解抛光_常州安谱金属制品有限公司 | 氧化铁红厂家-淄博宗昂化工 | crm客户关系管理系统,销售管理系统,crm系统,在线crm,移动crm系统 - 爱客crm | 济南电缆桥架|山东桥架-济南航丰实业有限公司 | 水热合成反应釜-防爆高压消解罐-西安常仪仪器设备有限公司 | 高效节能电机_伺服主轴电机_铜转子电机_交流感应伺服电机_图片_型号_江苏智马科技有限公司 | R507制冷剂,R22/R152a制冷剂厂家-浙江瀚凯制冷科技有限公司 | 小型铜米机-干式铜米机-杂线全自动铜米机-河南鑫世昌机械制造有限公司 | 儋州在线-儋州招聘找工作、找房子、找对象,儋州综合生活信息门户! | 二手光谱仪维修-德国OBLF光谱仪|进口斯派克光谱仪-热电ARL光谱仪-意大利GNR光谱仪-永晖检测 | 小型UV打印机-UV平板打印机-大型uv打印机-UV打印机源头厂家 |松普集团 | 传递窗_超净|洁净工作台_高效过滤器-传递窗厂家广州梓净公司 | 铸铁平台,大理石平台专业生产厂家_河北-北重机械 | 成都热收缩包装机_袖口式膜包机_高速塑封机价格_全自动封切机器_大型套膜机厂家 | 电杆荷载挠度测试仪-电杆荷载位移-管桩测试仪-北京绿野创能机电设备有限公司 |