您的位置:

QtConcurrent::run:高效并行编程实现方式

一、 QtConcurrent::run简介

QtConcurrent是Qt框架中的一个模块,旨在提供方便且高效的并行编程实现方式。其中QtConcurrent::run是使用最广泛的方法。调用QtConcurrent::run时,将会自动将函数以一个新的线程运行,并将函数返回结果传递回主线程。

QtConcurrent::run的用法简单,仅需传递一个函数指针和函数参数,即可使用多线程的方式运行函数。使用QtConcurrent::run可以充分利用多核处理器,提高程序效率,同时避免了使用底层多线程时的一些容易出现的线程安全问题。

二、 使用QtConcurrent::run进行多线程编程

我们来看一个简单的例子,假设我们需要对一个字符串进行加密处理:


QString encrypt(QString str, int key) {
    for(int i = 0; i < str.length(); ++i) {
        str[i] = str[i] ^ key;
    }
    return str;
}

接下来,我们通过QtConcurrent::run在新线程中运行这个函数:


QFuture
    future = QtConcurrent::run(encrypt, "hello world", 42);

   

使用QtConcurrent::run时,它不会占用主线程的时间,而是会自动启用一个新的线程去执行函数,这个新的线程会在后台运行,直到函数执行完毕并返回结果。

在这里我们用QFuture保存异步操作的结果,QFuture表示未来的结果,这个结果可以在需要的时候被获取。

下面,我们通过QFuture对象获取异步操作的结果:


QString result = future.result();

在需要异步操作结果的地方使用future.result()来获取异步操作结果,如果异步操作没有完成,future.result()就会一直等待,直到异步操作完成为止。

三、使用QtConcurrent::run实现线程安全的容器操作

除了简单的函数运算外,QtConcurrent::run还可以用于执行容器的并行操作,比如在多线程中对QList的修改操作。

在多线程中,对容器的修改操作需要考虑到线程安全,以避免出现竞态条件等线程安全问题。一般的解决方法是添加互斥锁,但互斥锁会导致线程的等待与阻塞,降低程序的效率。

使用QtConcurrent::run可以方便地实现线程安全的容器操作,使用QtConcurrent::run时需要将需要并行执行的操作函数写成可重入的函数。

以下是一个使用QtConcurrent::run实现并行容器操作的例子,这里我们实现一个在多线程中并行地对一个QList进行排序的操作。


void sort(QList
   & list) {
    std::sort(list.begin(), list.end());
}

void parallelSort(QList
    & list) {
    QVector
     
      > futures;
    const int threadCount = QThread::idealThreadCount();
    const int chunkSize = list.size() / threadCount;
    int begin = 0, end = chunkSize;
    for(int i = 0; i < threadCount; ++i) {
        if(i == threadCount - 1) {
            end = list.size();
        }
        futures.append(QtConcurrent::run(sort, list.mid(begin, end - begin)));
        begin = end;
        end += chunkSize;
    }
    for(int i = 0; i < futures.size(); ++i) {
        futures[i].waitForFinished();
    }
    std::inplace_merge(list.begin(), list.begin() + chunkSize, list.end());
}

      
     
    
   

parallelSort函数中,我们将要排序的QList按照QThread::idealThreadCount()的数量进行分割,并将每个子序列交给一个新线程去执行排序操作。

执行完所有子线程后,我们使用std::inplace_merge函数合并所有的排好序的子序列。

四、QtConcurrent::run的使用注意事项

虽然QtConcurrent::run提供了一种方便且高效的多线程编程实现方式,但其也有其适用的范围和使用注意事项:

  1. 线程池内的线程更加倾向于长期存在,不要把耗时很短的任务放进线程池中,可能会导致线程池耗费过多的系统资源,不利于程序性能。
    
    // 不建议写法
    for (int i = 0; i < 100; ++i) {
        QtConcurrent::run([](){ /* do something */ });
    }
    
  2. QtConcurrent::run会在新线程中自动执行函数,但也可以使用QThread手动地创建新线程。
    
    QThread* thread1 = new QThread();
    MyObject* object1 = new MyObject();
    object1->moveToThread(thread1);
    QObject::connect(thread1, &QThread::started, object1, &MyObject::mySlot);
    thread1->start();
    
  3. 使用QtConcurrent::run时需要注意线程安全,避免并发操作数据时出现竞争问题。
  4. QtConcurrent::run可以将函数的返回结果传递到主线程,但是如果异步操作返回的结果需要马上使用,应使用QFuture::result()来等待异步操作的结束并获取结果,否则可能会在异步操作没结束前使用该结果。
    
    QString result = QtConcurrent::run(someFunction, params).result();
    

五、结语

QtConcurrent::run是Qt框架中提供的一种方便、高效的多线程编程实现方式。使用QtConcurrent::run可以避免使用底层多线程时的一些容易出现的线程安全问题,并充分利用多核处理器提高程序效率。

在使用QtConcurrent::run时需要注意线程安全,合理地使用QThread,避免将耗时很短的任务放进线程池中,正确地使用异步的结果等技巧,可以充分发挥QtConcurrent::run的优势。