Qt Literacy-Qt Concurrent Overview

I. Overview

The QtConcurrent namespace provides some high-level APIs.You can write multiple threads without using low-level thread primitives such as mutexes, read-write locks, wait conditions, or semaphores. Thread program. Programs written using QtConcurrent automatically adjust the number of threads used based on the number of available processor cores. This means that applications written today will continue to scale when deployed on multi-core systems in the future.

QtConcurrent includes functional programming style APIs for parallel list processing, including MapReduce and FilterReduce implementations for shared memory (non-distributed) systems, and classes for managing asynchronous computations in GUI applications:

  • Concurrent Map and Map-reduce
    QtConcurrent::map() Apply a function to each item in the container, modifying these in place Item(not returned, modified in place).
    QtConcurrent::mapped() Similar to map(), except that it returns a new container with modifications(Return for modification).
    QtConcurrent::mappedReduced() Similar to map(), except that the modified result is simplified or collapsed into a single result.
  • Concurrent Filter and Filter-reduce
    QtConcurrent::filter() Removes all items from the container based on the result of the filter function.
    QtConcurrent::filtered() Similar to filter(), except that it returns a new container containing the filtered results.
    QtConcurrent::filteredReduced() Similar to filtered(), except that the filtered results are simplified or collapsed into a single result.
  • Concurrent Run (Concurrent Run)
    QtConcurrent::run() Run in another thread function.
  • QFuture represents the result of asynchronous calculation.
  • QFutureIterator allows iteration through the results obtained by QFuture.
  • QFutureWatcher allows monitoring QFutures using signals and slots.
  • QFutureSynchronizer is a convenience class that can automatically synchronize multiple QFutures.

Qt Concurrent supports several stl -compatible container and iterator types, but is best suited for Qt containers with random access iterators, such as QList or QVector. The map and filter functions accept containers and begin/end iterators.

STL iterator support overview:

iterator type Example class Support status
input iterator not support
output iterator not support
forward iterator std:: slist support
bidirectional iterator QLinkedList, std::list support
random access iterator QList, QVector, std::vector Support and recommendations

In cases where Qt Concurrent is iterating over a large number of lightweight items, random access iterators can be faster because they allow jumping to any point in the container. Additionally, using random access iterators allows Qt Concurrent to provide progress information via QFuture::progressValue() and QFutureWatcher::progressValueChanged().

Non-inplace modifying functions, such as mapped() and filtered(), make a copy of the container when called. If using an STL container, this copy operation may take some time, in which case Qt recommends specifying start and end iterators for the container.

2. Concurrent Map and Map-reduce

The QtConcurrent::map(), QtConcurrent::mapped(), and QtConcurrent::mappedReduced() functions run calculations in parallel on the items in a sequence (such as a QList or QVector). QtConcurrent::map() modifies the sequence in place, QtConcurrent::mapped() returns a new sequence containing the modified content, and QtConcurrent::mappedReduced() returns a result.

These functions are part of the Qt Concurrent framework.

Each function above has a blocking variable that returns the final result instead of a QFuture. We can use them just like we use async variables.

  QList<QImage> images = ...;

  // Each call blocks until the entire operation is finished.
  QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

  QtConcurrent::blockingMap(images, scale);

  QImage collage = QtConcurrent::blockingMappedReduced(images, scaled, addToCollage);

Note that the result types above are not QFuture objects, but real result types (QList and QImage in this case).

1. Concurrent Map

QtConcurrent::mapped() accepts an input sequence and a mapping function. The mapping function is then called for each item in the sequence and a new sequence containing the return value of the mapping function is returned.

The format of the map function must be:

 U function(const T &t);

T and U can be of any type (they can even be the same type), but T must match the type stored in the sequence. The function returns the modified or mapped content.

This example shows how to apply a proportional function to all items in a sequence:

  QImage scaled(const QImage &image)
  {
    
    
      return image.scaled(100, 100);
  }

  QList<QImage> images = ...;
  QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scaled);

The results of this Map are available via QFuture. For more information on how to use QFuture in your application, see the QFuture and QFutureWatcher documentation.

If we want to modify the sequence in place, use QtConcurrent::map(). The map function must be of the form:

 U function(T &t);

Note that the return value and return type of the map function are not used.

Using QtConcurrent::map() is similar to using QtConcurrent::mapped():

  void scale(QImage &image)
  {
    
    
      image = image.scaled(100, 100);
  }

  QList<QImage> images = ...;
  QFuture<void> future = QtConcurrent::map(images, scale);

Since the sequence is modified in-place, QtConcurrent::map() will not return any results via QFuture. However, we can still use QFuture and QFutureWatcher to monitor the state of the Map.

2. Concurrent Map-Reduce

QtConcurrent::mappedReduced() is similar to QtConcurrent::mapped(), but instead of returning a sequence with new results, it uses the reduce function to combine the results into a single value.

QFuture<ResultType> QtConcurrent:: mapappedreduced (const Sequence &sequence, MapFunctor mapFunction, ReduceFunctor reduceFunction, QtConcurrent::ReduceOptions ReduceOptions = ReduceOptions(UnorderedReduce | SequentialReduce))

Meaning: Call mapFunction once for each Item in sequence. The return value of each mapFunction is passed to reduceFunction, and finally a result is obtained.

The form of the reduce function must be:

V function(T &result, const U &intermediate)

T is the type of the final result and U is the return type of the mapping function. Note that the return value and return type of the reduce function are not used here.

Call QtConcurrent::mappedReduced() like this:

  void addToCollage(QImage &collage, const QImage &thumbnail)
  {
    
    
      QPainter p(&collage);
      static QPoint offset = QPoint(0, 0);
      p.drawImage(offset, thumbnail);
      offset += ...;
  }

  QList<QImage> images = ...;
  QFuture<QImage> collage = QtConcurrent::mappedReduced(images, scaled, addToCollage);

For each result returned by the map function, the reduce function will be called once and the intermediate values ​​should be merged into the result variable.

QtConcurrent::mappedReduced() guarantees that only one thread calls reduce at a time, so there is no need to use a mutex to lock the result variable.

The ReduceOptions enum provides a way to control the order in which reductions are performed. If you use QtConcurrent::UnorderedReduce (the default), the order is undefined, while QtConcurrent::OrderedReduce ensures reduction in the order of the original sequence.

3. Other API features

1. Use iterators instead of Sequences

Each of the above functions has a variant that accepts an iterator range instead of a sequence. Use them in the same way as sequence variants:

QList<QImage> images = ...;

QFuture<QImage> thumbnails = QtConcurrent::mapped(images.constBegin(), images.constEnd(), scaled);

// Map in-place only works on non-const iterators.
QFuture<void> future = QtConcurrent::map(images.begin(), images.end(), scale);

QFuture<QImage> collage = QtConcurrent::mappedReduced(images.constBegin(), images.constEnd(), scaled, addToCollage);

3. Blocking variables

Each function above has a blocking variable which returns the final result instead of a QFuture. We can use them just like we use async variables.

  QList<QImage> images = ...;

  // Each call blocks until the entire operation is finished.
  QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

  QtConcurrent::blockingMap(images, scale);

  QImage collage = QtConcurrent::blockingMappedReduced(images, scaled, addToCollage);

Note that the result types above are not QFuture objects, but real result types (QList and QImage in this case).

4. Use member functions

QtConcurrent::map(), QtConcurrent::mapped() and QtConcurrent::mappedReduced() accept pointers to member functions. The member function class type must match the type stored in the sequence:

  // Squeeze all strings in a QStringList.
  QStringList strings = ...;
  QFuture<void> squeezedStrings = QtConcurrent::map(strings, &QString::squeeze);

  // Swap the rgb values of all pixels on a list of images.
  QList<QImage> images = ...;
  QFuture<QImage> bgrImages = QtConcurrent::mapped(images, &QImage::rgbSwapped);

  // Create a set of the lengths of all strings in a list.
  QStringList strings = ...;
  QFuture<QSet<int> > wordLengths = QtConcurrent::mappedReduced(strings, &QString::length, &QSet<int>::insert);

Note that when using QtConcurrent::mappedReduced() you can freely mix normal functions and member functions:

  // Can mix normal functions and member functions with QtConcurrent::mappedReduced().

  // Compute the average length of a list of strings.
  extern void computeAverage(int &average, int length);
  QStringList strings = ...;
  QFuture<int> averageWordLength = QtConcurrent::mappedReduced(strings, &QString::length, computeAverage);

  // Create a set of the color distribution of all images in a list.
  extern int colorDistribution(const QImage &string);
  QList<QImage> images = ...;
  QFuture<QSet<int> > totalColorDistribution = QtConcurrent::mappedReduced(images, colorDistribution, QSet<int>::insert);

5. Use function objects

QtConcurrent::map(), QtConcurrent::mapped() and QtConcurrent::mappedReduced() accept function objects of the map function. These function objects can be used to add state to function calls. result_type typepedef must define the result type of the function call operator:

  struct Scaled
  {
    
    
      Scaled(int size)
      : m_size(size) {
    
     }

      typedef QImage result_type;

      QImage operator()(const QImage &image)
      {
    
    
          return image.scaled(m_size, m_size);
      }

      int m_size;
  };

  QList<QImage> images = ...;
  QFuture<QImage> thumbnails = QtConcurrent::mapped(images, Scaled(100));

For reduce functions, function objects are not directly supported. However, when the type of the reduction result is explicitly specified, a function object can be used:

  struct ImageTransform
  {
    
    
      void operator()(QImage &result, const QImage &value);
  };

  QFuture<QImage> thumbNails =
    QtConcurrent::mappedReduced<QImage>(images,
                                        Scaled(100),
                                        ImageTransform(),
                                        QtConcurrent::SequentialReduce);

6. Wrapping functions that accept multiple parameters

If we want to use the map function that accepts multiple parameters, we can use the lambda function or std::bind() to convert it to a function that accepts one parameter.
As an example we will use QImage::scaledToWidth():

  QImage QImage::scaledToWidth(int width, Qt::TransformationMode) const;

scaledToWidth accepts three parameters (including the "this" pointer) and cannot be used directly with QtConcurrent::mapped() because QtConcurrent::mapped() expects a function to accept one parameter. In order to use QImage::scaledToWidth() and QtConcurrent::mapped() we must provide a value for width and conversion mode:

  QList<QImage> images = ...;
  std::function<QImage(const QImage &)> scale = [](const QImage &img) {
    
    
      return img.scaledToWidth(100, Qt::SmoothTransformation);
  };
  QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scale);

三、Concurrent Filter and Filter-Reduce

The QtConcurrent::filter(), QtConcurrent::filtered(), and QtConcurrent::filteredReduced() functions perform parallel filtering of items in a sequence, such as a QList or QVector. QtConcurrent::filter() modifies the sequence in place, QtConcurrent::filtered() returns a new sequence containing the filtered content, and QtConcurrent::filteredReduced() returns a single result.
These functions are part of the Qt Concurrent framework.
Each function above has a blocking variable that returns the final result instead of a QFuture. You can use them just like the async variant.

  QStringList strings = ...;

  // each call blocks until the entire operation is finished
  QStringList lowerCaseStrings = QtConcurrent::blockingFiltered(strings, allLowerCase);

  QtConcurrent::blockingFilter(strings, allLowerCase);

  QSet<QString> dictionary = QtConcurrent::blockingFilteredReduced(strings, allLowerCase, addToDictionary);

Note that the result types above are not QFuture objects, but real result types (QStringList and QSet in this case).

1. Concurrency filter

QtConcurrent::filtered() accepts an input sequence and a filter function. This filter function is then called on each item in the sequence and returns a new sequence containing the filtered values.

The filter function must look like this:

bool function(const T &t);

T must match the type stored in the sequence. Returns true if the item should be kept; false if the item should be discarded.

This example shows how to retain all lowercase strings from a QStringList:

  bool allLowerCase(const QString &string)
  {
    
    
      return string.lowered() == string;
  }

  QStringList strings = ...;
  QFuture<QString> lowerCaseStrings = QtConcurrent::filtered(strings, allLowerCase);

The results of the filter can be obtained through QFuture. For more information on how to use QFuture in your application, see the QFuture and QFutureWatcher documentation.

If you want to modify a sequence in place, use QtConcurrent::filter():

  QStringList strings = ...;
  QFuture<void> future = QtConcurrent::filter(strings, allLowerCase);

Since the sequence is modified in-place, QtConcurrent::filter() will not return any results via QFuture. However, you can still use QFuture and QFutureWatcher to monitor the status of the filter.

2. Concurrent Filter-Reduce

QtConcurrent::filteredReduced() is similar to QtConcurrent::filtered(), but instead of returning a sequence containing the filtered results, the reduce function is used to combine the results into a single value.

The form of the reduce function must be:

V function(T &result, const U &intermediate)

T is the type of the final result and U is the type of the filtered items. Note that the return value and return type of the reduce function are not used here.

Call QtConcurrent::filteredReduced() like this:

  void addToDictionary(QSet<QString> &dictionary, const QString &string)
  {
    
    
      dictionary.insert(string);
  }

  QStringList strings = ...;
  QFuture<QSet<QString> > dictionary = QtConcurrent::filteredReduced(strings, allLowerCase, addToDictionary);

For each result saved by the filter function, the reduce function will be called once and the intermediate results should be merged into the result variable.

QtConcurrent::filteredReduced() guarantees that only one thread calls reduce at a time, so there is no need to use a mutex to lock the result variable. The ReduceOptions enum provides a way to control the order in which reductions are performed.

3. Other API features

1. Use iterators instead of Sequences

Each of the above functions has a variant that accepts an iterator range instead of a sequence. Use them in the same way as sequence variants:

  QStringList strings = ...;
  QFuture<QString> lowerCaseStrings = QtConcurrent::filtered(strings.constBegin(), strings.constEnd(), allLowerCase);

  // filter in-place only works on non-const iterators
  QFuture<void> future = QtConcurrent::filter(strings.begin(), strings.end(), allLowerCase);

  QFuture<QSet<QString> > dictionary = QtConcurrent::filteredReduced(strings.constBegin(), strings.constEnd(), allLowerCase, addToDictionary);

2. Use member functions

QtConcurrent::filter(), QtConcurrent::filtered() and QtConcurrent::filteredReduced() accept pointers to member functions. The member function class type must match the type stored in the sequence:

  // keep only images with an alpha channel
  QList<QImage> images = ...;
  QFuture<void> alphaImages = QtConcurrent::filter(images, &QImage::hasAlphaChannel);

  // retrieve gray scale images
  QList<QImage> images = ...;
  QFuture<QImage> grayscaleImages = QtConcurrent::filtered(images, &QImage::isGrayscale);

  // create a set of all printable characters
  QList<QChar> characters = ...;
  QFuture<QSet<QChar> > set = QtConcurrent::filteredReduced(characters, &QChar::isPrint, &QSet<QChar>::insert);

Note that when using QtConcurrent::filteredReduced() you can freely mix normal functions and member functions:

  // can mix normal functions and member functions with QtConcurrent::filteredReduced()

  // create a dictionary of all lower cased strings
  extern bool allLowerCase(const QString &string);
  QStringList strings = ...;
  QFuture<QSet<int> > averageWordLength = QtConcurrent::filteredReduced(strings, allLowerCase, QSet<QString>::insert);

  // create a collage of all gray scale images
  extern void addToCollage(QImage &collage, const QImage &grayscaleImage);
  QList<QImage> images = ...;
  QFuture<QImage> collage = QtConcurrent::filteredReduced(images, &QImage::isGrayscale, addToCollage);

3. Use function objects

QtConcurrent::filter(), QtConcurrent::filtered() and QtConcurrent::filteredReduced() accept function objects of filter functions. These function objects can be used to add state to function calls. result_type typepedef must define the result type of the function call operator:

  struct StartsWith
  {
    
    
      StartsWith(const QString &string)
      : m_string(string) {
    
     }

      typedef bool result_type;

      bool operator()(const QString &testString)
      {
    
    
          return testString.startsWith(m_string);
      }

      QString m_string;
  };

  QList<QString> strings = ...;
  QFuture<QString> fooString = QtConcurrent::filtered(strings, StartsWith(QLatin1String("Foo")));

For reduce functions, function objects are not directly supported. However, when the type of the reduction result is explicitly specified, a function object can be used:

  struct StringTransform
  {
    
    
      void operator()(QString &result, const QString &value);
  };

  QFuture<QString> fooString =
    QtConcurrent::filteredReduced<QString>(strings,
                                           StartsWith(QLatin1String("Foo")),
                                           StringTransform());

4. Wrapping functions that accept multiple parameters

If you want to use a filter function that accepts multiple parameters, you can use a lambda function or std::bind() to convert it to a function that accepts one parameter.
As an example, we use QString::contains():

bool QString::contains(const QRegularExpression &regexp) const;

QString::contains() accepts 2 parameters (including the "this" pointer) and cannot be used directly with QtConcurrent::filtered() because QtConcurrent::filtered() expects a function to accept one parameter. To use QString::contains() with QtConcurrent::filtered() we must provide a value for the regexp parameter:

  QStringList strings = ...;
  
  QFuture<QString> future = QtConcurrent::filtered(list, [](const QString &str) {
    
    
      return str.contains(QRegularExpression("^\\S+$")); // matches strings without whitespace
  });

四、Concurrent Run

The QtConcurrent::run() function runs a function in a separate thread. The return value of the function can be obtained through the QFuture API.

This function is part of the Qt Concurrent framework.

1. Run the function in a separate thread

To run a function in another thread, use QtConcurrent::run():

  extern void aFunction();
  QFuture<void> future = QtConcurrent::run(aFunction);

This will run the function in a separate thread obtained from the default QThreadPool. You can use the QFuture and QFutureWatcher classes to monitor the state of a function.

To use a dedicated thread pool, you can pass QThreadPool as the first parameter:

  extern void aFunction();
  QThreadPool pool;
  QFuture<void> future = QtConcurrent::run(&pool, aFunction);

2. Pass parameters to the function

Passing parameters to a function is done by adding the parameters to the QtConcurrent::run() call after the function name. For example:

  extern void aFunctionWithArguments(int arg1, double arg2, const QString &string);

  int integer = ...;
  double floatingPoint = ...;
  QString string = ...;

  QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);

A copy of each argument is made when calling QtConcurrent::run() and these values ​​are passed to the thread when it starts executing the function.

Changes to parameters after calling QtConcurrent::run() are not visible to the thread.

3. Return value from function

Any return value of a function can be obtained through QFuture:

  extern QString functionReturningAString();
  QFuture<QString> future = QtConcurrent::run(functionReturningAString);
  ...
  QString result = future.result();

As mentioned above, passing parameters is done like this:

  extern QString someFunction(const QByteArray &input);

  QByteArray bytearray = ...;

  QFuture<QString> future = QtConcurrent::run(someFunction, bytearray);
  ...
  QString result = future.result();

Note that the QFuture::result() function blocks and waits for the result to be available. Use QFutureWatcher to get notified when the function completes execution and the results are available.

4. Other API features

1. Use member functions

QtConcurrent::run() also accepts pointers to member functions. The first argument must be a const reference or pointer to a class instance. Passing by const reference is useful when calling const member functions; passing by pointer is useful for calling non-const member functions that modify the instance.

For example, calling QByteArray::split() (a const member function) in a separate thread does this:

  // call 'QList<QByteArray>  QByteArray::split(char sep) const' in a separate thread
  QByteArray bytearray = "hello world";
  QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
  ...
  QList<QByteArray> result = future.result();

Here's how to call a non-const member function:

  // call 'void QImage::invertPixels(InvertMode mode)' in a separate thread
  QImage image = ...;
  QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
  ...
  future.waitForFinished();
  // At this point, the pixels in 'image' have been inverted

2. Use Lambda functions

Calling the lambda function is done like this:

  QFuture<void> future = QtConcurrent::run([=]() {
    
    
      // Code in this block will run in another thread
  });
  ...

Guess you like

Origin blog.csdn.net/qq_43680827/article/details/133955717