Reputation: 9334
I have a collection (currently an std::list<MyClass*>
but it can be a std::map<int, MyClass*>
also) named my_objects
which contains pointers to MyClass
instances.
At the moment, I process the array like this:
for(std::list<MyClass*>::iterator iterator = my_objects.begin(), end = my_objects.end(); iterator != end; ++iterator) {
(*iterator)->someFunction();
// ...
}
someFunction
may change some of the properties of the current element, and may read some of the other elements properties. But there is no property, which is changed, and read by an another instance. So the result is the same regardless of the order of iteration.
I would like to rewrite this loop, to use four std::thread
, and process the first quarter of elements with the first thread, the second quarter with the thread, etc...
Is it possible to jump inside of the collection, and start the iteration there? Is it the recommended method to process a collection like this? If not, how should this be done?
Upvotes: 0
Views: 212
Reputation: 1879
The recommended method is to use an atomic counter to acquire unique objects for each thread to process. The thread function for each thread looks similar to this (pseudo code):
std::atomic<int> atomic_ctr(0);
void threadfunc() {
list_iterator iter = list.begin();
int current_list_pos=0;
int next_pos;
while((next_pos = atomic_ctr++) < list.size()) {
while (current_list_pos < next_pos) {
++iter;
++current_list_pos;
}
iter->func();
}
}
Upvotes: 1
Reputation: 118445
For starters, modern C++ code uses range iteration:
for (const auto &ptr:my_objects)
{
ptr->someFunction();
}
Cleaner, less typing, avoids common iteration pitfalls.
Now, as far as partitioning a std::list
, there is no std::list
method that returns an initial iterator somewhere in the middle of the list. With std::vector
's random access iterators this is trivial, but this is not what a std::list
is for. So, if you want to change your container to a std::vector
, this becomes a no-brainer.
However, it's not really hard to iterate over the entire list, but only process each n
th element:
size_t p=0;
for (const auto &ptr:my_objects)
{
if (p == 0)
ptr->someFunction();
p = (p+1) % 4;
}
So now, this thread invokes someFunction()
for every fourth element in the list, and one-fourth of all elements in the list.
So, all that's needed here are four threads iterating here, with the only difference is that the first thread's initial value of p
is set to 0, as shown; the second thread's initial value of p
is set to 1; and, of course, the third and the fourth's thread initial value of p
set to 2 and 3.
This will neatly divide the list into four equal parts, with list elements assigned to one of the four threads for processing.
Upvotes: 3
Reputation: 1447
In the simplest case, you can create a worker object (to be called inside a thread) such that you construct it with a reference to your container and additionally the range (element index or iterators) that it is supposed to change.
If there truly are no dependencies to the parts of the data that are changed by your workers, this should work, even if it is a hack.
Otherwise, you will have to create a kind of wrapper object around your container that implements a locking mechanism for reading and writing of specific elements (I would suggest boost::upgrade_lock).
Your threaded worker objects would then need to talk to this (shared) object for read and write access to your container.
Upvotes: 0