I was writing code for the following parallel processing task:
- A
std::vector<T>contains data items that need to be processed - A function
process_data<T&>does that processing on such a single data item
In my software I want to do this for different types T, so I wrote a template class:
#include <mutex>
#include <thread>
#include <vector>
// Parallel processing class.
template <class T>
class parallel_processing {
public:
// Do parallel processing for all items in the vector.
void do_parallel_processing(std::vector<T>& items,
void (*item_processor)(T&),
size_t thread_count = 1)
{
// Check if we should do sequential processing after all.
if (thread_count <= 1) {
for (size_t i = 0; i < items.size(); i++)
item_processor(items[i]);
return;
}
// Proceed with parallel processing.
item_processor_ptr = item_processor;
items_ptr = &items;
next_item_index = 0;
// Spawn all threads.
std::vector<std::thread> threads;
for (size_t i = 0; i < thread_count; i++)
threads.push_back(std::thread(item_thread_worker));
// The current thread should also work hard. This has an advantage: calling join()
// (see below) blocks the thread, costing time. Method 'item_thread_worker' however
// only returns if all items are processed and thus all threads must also have
// finished (or are busy with their last item...).
item_thread_worker();
// Wait for all threads to finish and call join on them.
for (auto& this_thread : threads)
this_thread.join();
}
private:
// Get the next index to process.
int get_next_item_index()
{
const std::lock_guard<std::mutex> lock(next_item_index_mutex);
// Check if we're already done.
if (next_item_index >= (int)items_ptr->size())
return -1;
// Next index (first return, then increment).
return next_item_index++;
}
// Thread-worker method for items.
void item_thread_worker()
{
int item_index;
// Keep on processing while not all items are processed.
while ((item_index = get_next_item_index()) >= 0)
item_processor_ptr((*items_ptr)[item_index]);
}
// Properties.
std::mutex next_item_index_mutex; // For thread-safe access to 'next_item_index'.
int next_item_index; // Identifies the next item index to process.
void (*item_processor_ptr)(T& items); // The item processing function.
std::vector<T>* items_ptr; // Pointer to the vector with items to be processed.
};
The idea is simple and worked when it was not yet in a template class but separate functions but then of course could only be coded for a single type T:
- A number of threads is started and they all run the same worker method
- The workers pick a data item to be processed from the
std::vector<T>, and call the function to process the selected item until all items are processed
The compiler (VS2019) complains about the line:
threads.push_back(std::thread(item_thread_worker));
‘use & to create a pointer to a member’
So I tried threads.push_back(std::thread(&item_thread_worker)); which gives me the error:
”&’: illegal operation on bound member function expression’
So I tried all kind of things: with (), with the class in front ¶llel_processing<T>:: or ¶llel_processing:: but all I get are different errors…
My knowledge about C++ is clearly not enough to solve this, help is appreciated.
>Solution :
As item_thread_worker is a non-static member function, it needs a object to be called with.
When you create your threads, you don’t specify any objects. Those objects (which becomes the this pointer inside the functions) are passed as a hidden "first" argument.
Another point is that to get a pointer to a member function, you must use the pointer-to operator &. Unlike non-member functions, member functions do not decay to pointers to themselves. And you need to use the full scope, with the class-name.
So to create a thread using a non-static member function, that should be called on this object, you need to do std::thread(¶llel_processing::item_thread_worker, this).