Multi-thread in Qt

目录

Qt 多线程概览

如何使用

创建线程

启动线程

终止线程

停止QThread

停止QRunnable

停止QtConcurrent::run()

QThread 实现细节

QThread::start()

QThreadPrivate::start()

QThreadPrivate::finish()

QThread::wait()

QThreadPool

工作流程

源码分析

QThreadPool::start

QThreadPoolPrivate::tryStart

QThreadPoolThread::run

QThreadPoolPrivate::enqueueTask

其它关键函数

QtConcurrent 简介

例子 --- 把序列里小写的字符串加入词典

代码解析

ThreadEngineBase::startBlocking

forThreadFunction()

Blocksize怎么调整

参考文献


Qt 多线程概览

生命周期

开发任务

解决方案

 

一次调用

 

在另一个线程中运行一个函数,函数完成时退出线程

1.编写函数,使用QtConcurrent::run 运行它

2.派生QRunnable,使用QThreadPool::globalInstance()->start() 运行它

3.派生QThread,重新实现QThread::run() ,使用QThread::start() 运行它

一次调用

需要操作一个容器中所有的项。使用处理器所有可用的核心。

QtConcurrent 提供了map()函数来将操作应用到容器中的每一个元素,提供了fitler()函数来选择容器元素,以及指定reduce函数作为选项来组合剩余元素。

一次调用

一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。

使用QThread,重新实现run函数并根据需要发送信号。使用信号槽的queued连接方式将信号连接到GUI线程的槽函数。

持久运行

生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。

派生一个QObject对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中(moveToThread())并通过queued方式连接的信号槽进行通讯。

持久运行

生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。

同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer是一个替代。

QSocketNotifier 用来监听系统文件操作,将操作转换为Qt事件进入系统的消息循环队列。并调用预先设置的事件接受函数,处理事件。

如何使用

创建线程

class LoadResultWorker : public QThread {
    Q_OBJECT
protected:
    virtual void run();      // 重写run()函数
};
 
m_worker.reset(new LoadResultWorker());

启动线程

QThread

m_worker->start();

 

QRunable

class Work : public QRunnable
{
public:
    void run()
    {
        qDebug() << "Hello from thread " << QThread::currentThread();
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    Work work;
    work.setAutoDelete(false);  // 若设为true,QThreadPool会delete work;
    QThreadPool *threadPool = QThreadPool::globalInstance();
    threadPool->start(&work);   // 把QRunnable放进线程池执行
    qDebug() << "hello from GUI thread " << QThread::currentThread();
    threadPool->waitForDone();  // 等待所有线程结束
    return 0;
}

 

QtConcurrent::run()

void hello()
{
    qDebug() << "Hello from thread " << QThread::currentThread();
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QFuture future = QtConcurrent::run(hello);
    qDebug() << "hello from GUI thread " << QThread::currentThread();
    future.waitForFinished();
    return 0;
}

QtConcurrent::run(...) 可接收函数参数和返回值,例如:

QString someFunction(arg1, arg2, ...);

QFuture future = QtConcurrent::run(someFunction, arg1, arg2, ...);

QString result = future.result(); // 调用result()会阻塞直到run()运行结束

终止线程

停止QThread

有事件循环

在run()函数中调用exec() 启动事件循环,用exit() / quit() 退出线程

无事件循环

1.直接调用terminate(), 强制退出线程,不保存线程产生的数据,不推荐

2.设置stop flag,在run()函数读取stop flag,如果为真,则退出线程,此方法最常用。

void deviceThreadObject::stop()
{
	if (isFinished()) {
		return;
	}
	{
    	QMutexLocker locker(m_mutex);
    	m_stopped = true;
	}
	wait(); // 等待线程结束
}

void deviceThreadObject::run()
{
    m_stopped = false;
    while(true)
    {
        // The core work of your thread...

        // Check to see if we were stopped
        {
            QMutexLocker locker(m_mutex);
            if(m_stopped)
                break;
        }// locker goes out of scope and releases the mutex
    }
}

3.Qt 5.2以上,直接调用requestInterruption() / isInterruptionRequested()

停止QRunnable

QRunnable只是一个runner,不建议也不能stop它。

停止QtConcurrent::run()

此函数返回一个QFuture,QFuture提供cancel(), pause(), resume(), waitForFinished() 等函数控制线程的状态。

QThread 实现细节

QThread::start()

void QThread::start(Priority priority)
{
    Q_D(QThread);

    QMutexLocker locker(&d->mutex); // 修改d->running, d->finished 等变量,所以要加锁
    if (d->running)
        return;

    d->running = true;
    d->finished = false;
    d->terminated = false;         // 调用terminate()时会设为true
    d->returnCode = 0;
    d->exited = false;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为detached,运行结束后自动释放内存
	...
	...

    if (d->stackSize > 0) {       // 如果线程的栈大小被定义了,就使用它,否则就由操作系统自己决定。
		#if defined(_POSIX_THREAD_ATTR_STACKSIZE) && (_POSIX_THREAD_ATTR_STACKSIZE-0 > 0)
        int code = pthread_attr_setstacksize(&attr, d->stackSize); // 设置线程的栈大小
		#else
        int code = ENOSYS; // stack size not supported, automatically fail
		#endif // _POSIX_THREAD_ATTR_STACKSIZE

        if (code) {
            qWarning("QThread::start: Thread stack size error: %s",  qPrintable(qt_error_string(code)));

            // we failed to set the stacksize, and as the documentation states,
            // the thread will fail to run...
            d->running = false;
            d->finished = false;
            return;
        }
    }

    int code = pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this);  // 创建线程, 线程入口是QThreadPrivate::start
    if (code == EPERM) {
        // caller does not have permission to set the scheduling
        // parameters/policy
		#ifndef Q_OS_SYMBIAN
        	pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
		#endif
        code =  pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this); // 如果没有权限,就修改线程属性,再创建一次

    }
    pthread_attr_destroy(&attr);  // 销毁线程属性对象
    if (code) {
        qWarning("QThread::start: Thread creation error: %s", qPrintable(qt_error_string(code)));
        d->running = false;
        d->finished = false;
        d->thread_id = 0;

    }
}

QThreadPrivate::start()

void *QThreadPrivate::start(void *arg)
{
    // Symbian Open C supports neither thread cancellation nor cleanup_push.
#ifndef Q_OS_SYMBIAN
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 禁止取消线程
    pthread_cleanup_push(QThreadPrivate::finish, arg);   // 设置线程结束后的清理操作
#endif

    QThread *thr = reinterpret_cast(arg);
    QThreadData *data = QThreadData::get2(thr);
    // do we need to reset the thread priority?
    if (int(thr->d_func()->priority) & ThreadPriorityResetFlag) {
        thr->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag));  // 设置优先级
    }

    set_thread_data(data);
    data->ref();  // QThreadData 使用引用计数
    data->quitNow = false; // 事件循环开始,quitNow 设为false;事件循环结束,quitNow 设为true

    // ### TODO: allow the user to create a custom event dispatcher
    createEventDispatcher(data);                         // 创建事件分发器
    emit thr->started();                                 // 发送“线程开始了”的信号

#ifndef Q_OS_SYMBIAN
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 允许取消线程
    pthread_testcancel();                                // 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在响应取消请求
#endif
    thr->run();											// 执行自己写的run函数

#ifdef Q_OS_SYMBIAN
    QThreadPrivate::finish(arg);
#else
    pthread_cleanup_pop(1);                              // 运行结束,进行清理操作
#endif
    return 0;
}

QThreadPrivate::finish()

void QThreadPrivate::finish(void *arg)
{
    QThread *thr = reinterpret_cast(arg);
    QThreadPrivate *d = thr->d_func();
    d->mutex.lock();
    d->priority = QThread::InheritPriority;
    d->running = false;
    d->finished = true; // 运行结束,设置相关状态
    if (d->terminated)
        emit thr->terminated();

    d->terminated = false;
    emit thr->finished();  // 发送结束信号

    if (d->data->eventDispatcher) {     // 清理事件循环相关对象
        d->data->eventDispatcher->closingDown();
        QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher;
        d->data->eventDispatcher = 0;
        delete eventDispatcher;
    }

    ...

    d->thread_done.wakeAll(); // 唤醒wait()函数,退出线程
    d->mutex.unlock();
}

QThread::wait()

bool QThread::wait(unsigned long time)

{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);


    if (d->thread_id == pthread_self()) {
        qWarning("QThread::wait: Thread tried to wait on itself");  // 不能等自己
        return false;
    }

    if (d->finished || !d->running)
        return true;


    while (d->running) {
		...
        if (!d->thread_done.wait(locker.mutex(), time))     // QWaitCondition, 等待工作线程唤醒
            return false;
    }

    return true;
}

QThreadPool

经常性地创建和销毁线程是昂贵的。为节省开支,希望已创建的线程能够被用来跑其它任务。QThreadPool 正是一系列可重用的线程集合。

前文提到,继承QRunnable并重写run() 函数,在调用QThreadPool::globalInstance()->start(), 即可让run()运行在另一个线程。

先来看看QThreadPoolPrivate数据结构(Qt几乎每个类都用了桥接模式,也就是d指针,所以QThreadPool本身并没有什么可看的地方):

class QThreadPoolPrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(QThreadPool)
    friend class QThreadPoolThread;

public:
    QThreadPoolPrivate();

    bool tryStart(QRunnable *task);							// 尝试执行一个任务
    void enqueueTask(QRunnable *task, int priority = 0);    // 当前没有可用线程时,把任务放进队列等待
    int activeThreadCount() const;

    void tryToStartMoreThreads();
    bool tooManyThreadsActive() const;

    void startThread(QRunnable *runnable = 0);
    void reset();
    void waitForDone();
    bool startFrontRunnable();
    void stealRunnable(QRunnable *);

    mutable QMutex mutex;                          // 用于同步的互斥锁
    QWaitCondition runnableReady;   			   // 条件变量,当有新任务加入队列时,用来通知那些正在等待的线程
    QSet allThreads;		   // 记录所有创建了的线程
    QQueue expiredThreads;    // 记录所有过期的,还有利用价值的线程
    QList > queue;         // 任务队列,如果当前没有可用的线程了,就先把它放到队列中,int 表示的是任务的优先级
    QWaitCondition noActiveThreads;                // 条件变量,waitForDone()里会一直等待,直到当前没有活跃的线程,就被唤醒,说明所有线程都已经跑完,可以安全退出

    bool isExiting;       // 是否正在退出, isExiting在调用reset()函数的时候被设为true
    int expiryTimeout;    // 线程等候新任务的时间,默认值是30秒 
    int maxThreadCount;   // 最大线程数量,默认值是QThread::idealThreadCount(), this is done by querying the number of processor cores, both real and logical, in the system
    int reservedThreads;  // 保留的线程数
    int waitingThreads;   // 随时待命的线程数
    int activeThreads;    // 当前活跃的线程数
};

工作流程

源码分析

 

QThreadPool::start

void QThreadPool::start(QRunnable *runnable, int priority)
{
    if (!runnable)
        return;

    Q_D(QThreadPool);
    QMutexLocker locker(&d->mutex);          // 加锁
    if (!d->tryStart(runnable))              // 尝试执行runnable
        d->enqueueTask(runnable, priority);  // 尝试失败,先放到队列里等着
}

 

QThreadPoolPrivate::tryStart

bool QThreadPoolPrivate::tryStart(QRunnable *task)
{
    if (allThreads.isEmpty()) {
        // always create at least one thread
        startThread(task);				// 如果一个线程都没有,就创建一个新线程,并执行任务
        return true;
    }

    // can't do anything if we're over the limit
    if (activeThreadCount() >= maxThreadCount)   // 如果当前运行的线程数已经大于或等于线程容量,就返回
        return false;
    if (waitingThreads > 0) {				     // 如果当前有正在等待的线程,就放进任务队列里,并唤醒一个。正在等待线程就会从队列里取任务执行
        // recycle an available thread
        --waitingThreads;
        enqueueTask(task);
        return true;
    }

    if (!expiredThreads.isEmpty()) {			// 如果当前有快要过期的线程,就取出一个,用它来执行任务
        // restart an expired thread
        QThreadPoolThread *thread = expiredThreads.dequeue();   // QThreadPoolThread 继承于前文提到的QThread
        Q_ASSERT(thread->runnable == 0);
        ++activeThreads;
        if (task->autoDelete())
            ++task->ref;
        thread->runnable = task;
        thread->start();
        return true;
    }

    // start a new thread
    startThread(task);				// 如果以上条件都不符合,就创建一个新线程,并执行任务
    return true;
}

 

QThreadPoolThread::run

线程启动后,最终会调用QThreadPoolThread::run() 函数:

void QThreadPoolThread::run()
{
    QMutexLocker locker(&manager->mutex);
    for(;;) {			// 死循环,退出的条件是当前线程变成过期(expired)的
        QRunnable *r = runnable;
        runnable = 0;

        do {
            if (r) {
                const bool autoDelete = r->autoDelete();
                // run the task

                locker.unlock();
#ifndef QT_NO_EXCEPTIONS
                try {
#endif
                    r->run(); 		// 调用QRunnable::run()
#ifndef QT_NO_EXCEPTIONS
                } catch (...) {
                    qWarning("Qt Concurrent has caught an exception thrown from a worker thread.\n"
                             "This is not supported, exceptions thrown in worker threads must be\n"
                             "caught before control returns to Qt Concurrent.");
                    registerTheadInactive();
                    throw;
                }
#endif
                locker.relock();
                if (autoDelete && !--r->ref)
                    delete r;	  // 调用结束后,如果引用计数变为0,就释放runnable
            }


            // if too many threads are active, expire this thread
            if (manager->tooManyThreadsActive())
                break;

            r = !manager->queue.isEmpty() ? manager->queue.takeFirst().first : 0;  // 如果队列中还有任务,就继续取出来执行
        } while (r != 0);


        if (manager->isExiting) {	// 如果线程池正在退出,就返回
            registerTheadInactive();
            break;
        }


        // if too many threads are active, expire this thread
        bool expired = manager->tooManyThreadsActive();
        if (!expired) {
            ++manager->waitingThreads;														// 等待的线程数加1
            registerTheadInactive();														// 活跃的线程数减1
            // wait for work, exiting after the expiry timeout is reached
            expired = !manager->runnableReady.wait(locker.mutex(), manager->expiryTimeout);  // 事情都做完了,挺闲的,再等一段时间,看看还有没有新的任务
            ++manager->activeThreads;    

            if (expired)																	// 如果等了好久都没有任务,就不再等了
                --manager->waitingThreads;
        }

        if (expired) {																		// 如果等了好久都没有任务,就把自己加到“将要过期”的线程队列中
            manager->expiredThreads.enqueue(this);
            registerTheadInactive();														// 相应地,把当前运行的线程数减1
            break;																			// 这个线程结束了,退出循环
        }
		// 运行到这里,说明等了一段时间刚好有新的任务下来了,就又执行for(;;)
    }
}

QThreadPoolPrivate::enqueueTask

 

void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority)
{
    if (runnable->autoDelete())
        ++runnable->ref;

    // put it on the queue
    QList >::iterator at =
        qUpperBound(queue.begin(), queue.end(), priority);
    queue.insert(at, qMakePair(runnable, priority));		// 把任务放进队列
    runnableReady.wakeOne();								// 唤醒一个线程
}

其它关键函数

 

int QThreadPoolPrivate::activeThreadCount() const
{
    // To improve scalability this function is called without holding 
    // the mutex lock -- keep it thread-safe.
    return (allThreads.count()		// 创建了的线程总数 - 将要过期的线程数 - 等待的线程数 + 保留的线程数
            - expiredThreads.count()
            - waitingThreads
            + reservedThreads);    // 这个预留的线程数通过reserveThread()增加1,通过releaseThread()减少1
}

这个函数使得activeThreadCount() 有可能大于maxThreadCount, 前文提到,当activeThreadCount() >= maxThreadCount时,task不会立即执行,而是放到任务队列中等待。

 

/*!
    Reserves one thread, disregarding activeThreadCount() and maxThreadCount().
    Once you are done with the thread, call releaseThread() to allow it to be
    reused.

    \note This function will always increase the number of active threads.
    This means that by using this function, it is possible for
    activeThreadCount() to return a value greater than maxThreadCount() .
    \sa releaseThread()
 */
void QThreadPool::reserveThread()
{
    Q_D(QThreadPool);
    QMutexLocker locker(&d->mutex);
    ++d->reservedThreads;			// 对reservedThreads加1, 通俗的理解是,预留一个core用于做其它事情,这样就使得线程池不能把所有core都占用
}

/*!
    Releases a thread previously reserved by a call to reserveThread().
    \note Calling this function without previously reserving a thread
    temporarily increases maxThreadCount(). This is useful when a
    thread goes to sleep waiting for more work, allowing other threads
    to continue. Be sure to call reserveThread() when done waiting, so
    that the thread pool can correctly maintain the
    activeThreadCount().
    \sa reserveThread()
*/

void QThreadPool::releaseThread()		// 当任务跑完了,要把这个core释放出来,线程池就又可以继续使用了。
{
    Q_D(QThreadPool);
    QMutexLocker locker(&d->mutex);
    --d->reservedThreads;
    d->tryToStartMoreThreads();       // 可用的core多了一个,不用就是浪费,于是尝试着把队列中的任务拿出来执行
}

修改最大线程数

void QThreadPool::setMaxThreadCount(int maxThreadCount)
{
    Q_D(QThreadPool);
    QMutexLocker locker(&d->mutex);

    if (maxThreadCount == d->maxThreadCount)
        return;

    d->maxThreadCount = maxThreadCount;
    d->tryToStartMoreThreads();  // 有可能线程数被增多了,所以尝试执行队列中的任务
}
 
void QThreadPoolPrivate::tryToStartMoreThreads()
{
    // try to push tasks on the queue to any available threads
    while (!queue.isEmpty() && tryStart(queue.first().first))  // 不停地尝试执行队列中的任务
        queue.removeFirst();
}


bool QThreadPoolPrivate::tooManyThreadsActive() const
{
    const int activeThreadCount = this->activeThreadCount();
    return activeThreadCount > maxThreadCount && (activeThreadCount - reservedThreads) > 1;
}

QtConcurrent 简介

QtConcurrent提供了一组高级API,避免了用户使用底层的线程原语,比如QMutex, QWaitcondition等等。此外,某些异步操作使用QFuture 结构来保存返回的结果。

void blockingFilter ( Sequence & sequence, FilterFunction filterFunction )    // 对一个序列例如QVector, QList等的每一个元素执行filterFunction,filterFunction如果返回真,则保存到原来的序列中
Sequence blockingFiltered (const Sequence & sequence, FilterFunction filterFunction)  // 对一个序列例如QVector, QList等的每一个元素执行filterFunction,filterFunction如果返回真,则保存到新的序列中
Sequence blockingFiltered ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction ) // 与上类似

// 先filter序列的每个元素,如果filterFunction返回真,则传到reduceFunction进行后处理,最后返回一个具体类型
T blockingFilteredReduced ( const Sequence & sequence, FilterFunction filterFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )
// 与上类似
T blockingFilteredReduced ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )

// 对序列每个元素执行mapFunction,直接修改序列本身
void blockingMap ( Sequence & sequence, MapFunction function )
void blockingMap ( Iterator begin, Iterator end, MapFunction function )
 
// 对序列每个元素执行mapFunction,并返回计算结果
T blockingMapped ( const Sequence & sequence, MapFunction function )
T blockingMapped ( ConstIterator begin, ConstIterator end, MapFunction function )
 
// 对序列每个元素执行mapFunction,然后对mapFunction的结果执行reduceFucntion,并返回结果
T blockingMappedReduced ( const Sequence & sequence, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )
T blockingMappedReduced ( ConstIterator begin, ConstIterator end, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )

// 以下是上述函数的非阻塞版本
QFuture filter ( Sequence & sequence, FilterFunction filterFunction )
QFuture filtered ( const Sequence & sequence, FilterFunction filterFunction )
QFuture filtered ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction )
QFuture filteredReduced ( const Sequence & sequence, FilterFunction filterFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )
QFuture filteredReduced ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )
QFuture map ( Sequence & sequence, MapFunction function )
QFuture map ( Iterator begin, Iterator end, MapFunction function )
QFuture mapped ( const Sequence & sequence, MapFunction function )
QFuture mapped ( ConstIterator begin, ConstIterator end, MapFunction function )
QFuture mappedReduced ( const Sequence & sequence, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )
QFuture mappedReduced ( ConstIterator begin, ConstIterator end, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce )

QFuture run ( Function function, ... )

函数可分为两组,一组是阻塞的,另一组是非阻塞的。所有以blocking开头的函数都会阻塞直至序列每个元素都被处理完。

例子 --- 把序列里小写的字符串加入词典

QStringList strings = ...;

// 每次调用都会被阻塞,直到整个操作完成
QtConcurrent::blockingFilter(strings, allLowerCase);
QStringList lowerCaseStrings = QtConcurrent::blockingFiltered(strings, allLowerCase);
QSet dictionary = QtConcurrent::blockingFilteredReduced(strings, allLowerCase, addToDictionary);
 
bool allLowerCase(const QString &string)
{
	return string.lowered() == string;
}
 
void addToDictionary(QSet &dictionary, const QString &string)
{
	dictionary.insert(string);
}


不难发现,所有不以blocking开头的函数都返回QFuture. 和阻塞模式不同,QFuture提供cancel(), pause(), resume(), waitForFinished() 等函数控制线程的状态,这也是QtConcurrent相比于QThread和QThreadPool的一个优势。

代码解析

QtConcurrent 整个框架是基于QThreadPool的, 是一个独立的库,代码实现方面运行了大量模板,涉及的类较多且复杂,这里只选择部分代码讲解。

调用blockingMapReduce(...)后,经过一系列的模板以及各种转换函数后,会进到ThreadEngineBase::startBlocking 函数,ThreadEngine是QtConcurrent比较核心的一个类了。

 

                                                                                                                            

 

ThreadEngineBase::startBlocking

void ThreadEngineBase::startBlocking()
{
    start();
    barrier.acquire();  // ThreadEngineBarrier用QSemaphore来统计线程个数,并允许一个线程等待其他所有线程结束。
    startThreads(); 	// 启动多个线程

    bool throttled = false;
#ifndef QT_NO_EXCEPTIONS
    try {
#endif
        while (threadFunction() == ThrottleThread) {    // threadFunction执行计算操作
            if (threadThrottleExit()) {					// 如果线程被扼杀(比如用户调用QFuture::setPaused(true)),就退出循环
                throttled = true;
                break;
            }
        }

#ifndef QT_NO_EXCEPTIONS
    } catch (QtConcurrent::Exception &e) {			// 捕获异常
        handleException(e);
    } catch (...) {
        handleException(QtConcurrent::UnhandledException());
    }
#endif

    if (throttled == false) {						// 如果线程是正常退出的,就调用barrier.release()更新线程个数
        barrier.release();
    }


    barrier.wait();		// 因为是blocking,所以一直等待直到所有任务完成,如果是非阻塞版本的函数不会有这一句。
    finish();			// 善后工作
    exceptionStore.throwPossibleException();
}

 

其中threadFunction() 是关键,主线程和所有子线程都会调用它,而它则直接调用了 forThreadFunction(),如下:

forThreadFunction()

    ThreadFunctionResult forThreadFunction()
    {
        BlockSizeManager blockSizeManager(iterationCount);   // BlockSizeManager,用来控制线程每次在sequence里取多少个元素执行map函数, blocksize是变化的
        ResultReporter resultReporter(this);

        for(;;) {
            if (this->isCanceled())							// 每次迭代之前,检查一下是否被cancel了,如果是,就尽快退出。所以很容易理解QFuture::cancel()是异步的
                break;

            const int currentBlockSize = blockSizeManager.blockSize();   // 从BlockSizeManager里读取blocksize
            if (currentIndex >= iterationCount)							// 如果sequence已经遍历完了,就返回
                break;

            // Atomically reserve a block of iterationCount for this thread.
            const int beginIndex = currentIndex.fetchAndAddRelease(currentBlockSize);    // 取出当前index作为beginIndex,并currentIndex = currentIndex + currentBlockSize;
            const int endIndex = qMin(beginIndex + currentBlockSize, iterationCount);    // 设置endIndex的值

            if (beginIndex >= endIndex) {
                // No more work
                break;
            }

            this->waitForResume(); // 如果被暂停了,则等待(对于blocking函数,因为不返回QFuture,所以没法暂停)
								   // 这里也很容易理解QFuture::pause()是异步的
					//void QFutureInterfaceBase::waitForResume()
					//{
					//    // return early if possible to avoid taking the mutex lock.
					//    if ((d->state & Paused) == false || (d->state & Canceled))
					//        return;

					//    QMutexLocker lock(&d->m_mutex);
					//    if ((d->state & Paused) == false || (d->state & Canceled))
					//        return;

					//    // decrease active thread count since this thread will wait.
					//    QThreadPool::globalInstance()->releaseThread();  // 释放当前线程

					//    d->pausedWaitCondition.wait(&d->m_mutex);
					//    QThreadPool::globalInstance()->reserveThread();  // 等待结束, 重新预留一个线程
					//}

			if (shouldStartThread())
                this->startThread();

            const int finalBlockSize = endIndex - beginIndex; // block size adjusted for possible end-of-range
            resultReporter.reserveSpace(finalBlockSize);


            // Call user code with the current iteration range.
            blockSizeManager.timeBeforeUser();
            const bool resultsAvailable = this->runIterations(begin, beginIndex, endIndex, resultReporter.getPointer());  // 从beginIndex到endIndex,分别执行map()函数,并把结果放到resultReporter里,如果是mapReduce的话,还会对每个结果执行reduce函数
            blockSizeManager.timeAfterUser();              // 根据执行用户代码的时间来调整blocksize

            if (resultsAvailable)
                resultReporter.reportResults(beginIndex);

            // Report progress if progress reporting enabled.
            if (progressReportingEnabled) {					// 记录进度, QFuture有个函数progressValue()可以查询进度
                completed.fetchAndAddAcquire(finalBlockSize);  // completed是一个原子型的int类型,每次加上finalBlockSize
                this->setProgressValue(this->completed);
            }

            if (this->shouldThrottleThread())		// 如果调用了QFuture::setPaused(true), 则shouldThrottleThread()返回true
                return ThrottleThread;
        }
        return ThreadFinished;
    }

 

Blocksize怎么调整

for循环控制时间 等于 timeBeforeUser() 减去 上一次的timeAfterUser();

如果for循环控制时间 > 用户代码执行时间 / 100, 说明循环控制时间有点长了,则blocksize翻倍,直到达到最大的blocksize.

BlockSizeManager::BlockSizeManager(int iterationCount)
: maxBlockSize(iterationCount / (QThreadPool::globalInstance()->maxThreadCount() * 2)),    // 这里为什么要乘以2 ???
  beforeUser(0), afterUser(0),
  controlPartElapsed(MedianSize), userPartElapsed(MedianSize),
  m_blockSize(1)   // blocksize 初始值为1
{ }

// Records the time before user code.
void BlockSizeManager::timeBeforeUser()
{
    if (blockSizeMaxed())         // 如果已经达到最大blocksize,则不用再调整
        return;

    beforeUser = getticks();
    controlPartElapsed.addValue(elapsed(beforeUser, afterUser));
}

 // Records the time after user code and adjust the block size if we are spending
 // to much time in the for control code compared with the user code.
void BlockSizeManager::timeAfterUser()
{
    if (blockSizeMaxed())
        return;

    afterUser = getticks();
    userPartElapsed.addValue(elapsed(afterUser, beforeUser));

    if (controlPartElapsed.isMedianValid() == false)
        return;

    if (controlPartElapsed.median() * TargetRatio < userPartElapsed.median())   // 取多次测量结果的中位数,进行比较, 这里的TargetRatio为100
        return;

    m_blockSize = qMin(m_blockSize * 2,  maxBlockSize);    // blocksize翻倍

#ifdef QTCONCURRENT_FOR_DEBUG

    qDebug() << QThread::currentThread() << "adjusting block size" << controlPartElapsed.median() << userPartElapsed.median() << m_blockSize;

#endif

    // Reset the medians after adjusting the block size so we get
    // new measurements with the new block size.
    controlPartElapsed.reset();
    userPartElapsed.reset();
}

int BlockSizeManager::blockSize()
{
    return m_blockSize;					// 注意,这个类没有加锁,说明它不是跨线程的,每个线程的忙碌程度不同,执行用户代码的时间也不尽相同,所以没必要共享这个blocksize
}

用通俗的话总结一下blocksize调整的原因吧:

刚开始不知道用户的代码要执行多久,心想反正我有线程池,所以先每次执行一个元素试试,后来试了几次,发现用户代码执行很快,甚至和我的循环控制代码在一个数量级了,这时候我觉得我每次可以做更多事情了,于是说:我要打十个。当然我不能一直增加数量,因为也要给其它线程发挥一下它们的价值。

关于QtConcurrent的东西,先讲到这里,以后有时间再研究一下QFuture和ThreadEngine之间的交互细节。

参考文献

Qt thread basics

Multithreading Technologies in Qt

QThreadPool code analysis

QThreadPool::reserveThread() example

你可能感兴趣的:(Qt)