信号槽的多线程安全性讨论

讲正题之前首先需要了解几个基础知识点:

  • 信号槽的连接方式

connect()函数的参数如下:

QMetaObject::Connection QObject::connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)

最后的ConnectionType是一个缺省的参数,默认值AutoConnection

1. AutoConnection(自动连接)

    如果信号的发送者和信号的接受者对象同属一个线程,那么这个工作方式与Direct Connection(直连)相同;反之与Queued Connection(队列)相同。

2. Direct Connection (直接连接)

    当信号发出后,相应的槽函数将立即被调用;emit语句后的代码只能在对应槽函数执行完毕后被执行,信号与槽函数关系等同于函数调用,同步执行。

3. Queued Connection(队列连接).

    当信号发出后,发送到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才调用相应的槽函数。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕;此时信号被塞到信号队列里了,信号与槽函数关系类似于消息通信,异步执行

4. Blocking Queued Connection (阻塞队列连接)

    阻塞的队列连接。效果上和Direct Connection一样,但是机制上和Queued Connection用一样的信号队列去维护信号。这种连接方式主要用在跨线程调用信号槽的场景中想实现阻塞发送信号的效果,emit后等信号触发相应槽函数后才会继续执行emit后的代码;

    为什么会有这种需求呢,因为队列连接是异步的,当有多个信号依次触发的时候不能保证槽会被依次调用。比如a线程依次发出信号single脱裤子、single拉屎,但是b线程响应的时候依次触发了 slot拉屎、slot脱裤子,这种情况就比较糟糕了吧。Blocking Queued Connection可以帮我们避免这种尴尬。

5. Unique Connection (防重复连接)

    同一信号槽多次重复connect会造成一个信号发出后槽被多次调用,Unique Connection就是防止这个的,保证同一信号槽只连接一次。但这个功能比较鸡肋,因为比较规范的做法是不要多次connect,connect没用的时候可以使用disconnect释放连接。

   简而言之,信号槽实际效果上分两种,阻塞方式(直连,阻塞队列) 、非阻塞模式 (队列模式)。大多数情况下我们都不加第五个参数,所以默认参数下信号槽都在同一线程的时候为阻塞连接,在不同线程的时候为非阻塞连接。

  • 信号槽的归宿线程

    归宿线程这个词是我自己造的,意思就是信号或者槽在哪个线程里执行的。                                                                                    信号的归宿线程没什么疑问,在哪emit的就在哪个线程。信号槽在同一线程的时候也没什么多说的,此时槽函数就相当于一个普通的成员函数。                                                                                                                                                                                   跨线程的场景下当一个信号被触发后,槽函数是在哪个线程中响应的呢。这个其实也不复杂:槽函数所属的对象在哪个线程里创建的,槽函数就在相应线程中被执行,如果这个线程被阻塞了,那么这个槽函数也不会被执行,它是活在归宿线程里面的,响应信号的其实是归宿线程,槽函数只是一段执行代码。

  • QThread 对象槽函数的归宿线程

    QThread新建线程的时候,只有其run函数内的部分才属于新创建的线程,构造和析构都是在创建QThread对象的线程中执行的。所以如果一个QThread有槽函数的话,这个槽函数被触发的时候,是在创建QThread的线程中执行和QThread线程没什么关系。

 

信号槽的多线程安全性

   这个安全性就是当槽函数为不可重入函数的时候多线程并发执行的问题。槽函数是理论上是认线程安全的,因为单线程的时候一切都是被阻塞调用的不存在并发的问题。多线程下,槽对应的对象只属于一个线程 ,即使有多个线程发信号,此时也是在队列模式下阻塞运行,也不存在并发的问题。

   实际上有不安全的情况。如果一个线程对象的槽函数在run函数内被直接调用,因为run函数和槽函数所属的对象不在同一个线程中,此时会出现并发。

例:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 
#include 
#include 

class MyThreadB;

extern MyThreadB *pb;

class MyThreadA : public QThread
{
    Q_OBJECT
public:
    MyThreadA(QObject* parent=0);
    ~MyThreadA();
    void run();
signals:
    void send(QString);
};

class MyThreadB : public QThread
{
    Q_OBJECT
public:
    MyThreadB(QObject* parent=0);
    ~MyThreadB();
    void run();
public slots:
    void revice(QString);
};



#endif // MYTHREAD_H
#include "MyThread.h"
#include 
#include 

MyThreadB *pb = NULL;

MyThreadA::MyThreadA(QObject* parent):QThread(parent)
{
    qDebug()<<"MyThreadA parent Id:"<
#include 
#include "MyThread.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyThreadB *myThreadB = new MyThreadB();
    myThreadB->start();

    pb = myThreadB;

    MyThreadA *myThreadA = new MyThreadA();
    myThreadA->start();

    return a.exec();
}

当A线程触发信号的时候,此时槽函数并行运行了。

信号槽的多线程安全性讨论_第1张图片

    可以清楚的看到槽函数此时不是在同一个线程内执行的,线程关系一目了然。  

    这种情况是不具有线程安全性的,同样的情况也会出现在线程中使用定时器,定时器链接线程对象的槽函数的时候。可以给槽函数加锁来解决这种并发问题。还有一种取巧的方法,就是不要在run函数里直接调用槽函数而是改成emit触发的方式,这种情况下槽函数就都是在归宿线程里执行的了,也不会有并发的问题。

 

你可能感兴趣的:(qt,c++)