diff --git a/test/src/utils/threading/concurrent_vector_guard.cpp b/test/src/utils/threading/concurrent_vector_guard.cpp index 69480855..f4449840 100644 --- a/test/src/utils/threading/concurrent_vector_guard.cpp +++ b/test/src/utils/threading/concurrent_vector_guard.cpp @@ -30,9 +30,11 @@ #include "src/common.hpp" +#include "genesis/utils/math/random.hpp" #include "genesis/utils/threading/concurrent_vector_guard.hpp" #include "genesis/utils/threading/thread_pool.hpp" +#include #include #include @@ -89,3 +91,70 @@ TEST( Threading, ConcurrentVectorGuard ) } EXPECT_EQ( 0, cnt_wrong ); } + +TEST( Threading, VectorEntries ) +{ + // Test siez + size_t const num_threads = 10; + size_t const num_vecs = 1000; + size_t const max_length = 1000; + + auto const seed = ::time(nullptr); + permuted_congruential_generator_init( seed ); + LOG_INFO << "Seed: " << seed; + + // Fill a vector with vectors of different lengths, with values that are all 1. + // Then, we can add up those entries to get our expected number of processed elements. + size_t exp_num_elem = 0; + auto data = std::vector>( num_vecs ); + for( auto& vec : data ) { + vec.resize( permuted_congruential_generator( max_length ), 1 ); + exp_num_elem += vec.size(); + } + + // Now we spin up some threads and erase elements in parallel from the vector, starting + // at different offsets for speed. Each thread starts processing elements, and removes + // them from their vec within data, until empty, and then moves to the next one. + auto thread_pool = std::make_shared( num_threads ); + auto vector_guard = ConcurrentVectorGuard( num_vecs ); + std::atomic num_elem = 0; + for( size_t t = 0; t < num_threads; ++t ) { + thread_pool->enqueue_detached([&, t]() + { + // Get equally distributed starting group indices. + size_t const start_group_idx = t * num_vecs / num_threads; + size_t cur_group_idx = start_group_idx; + + while( true ) { + // Lock the vector that we are currently operating on. + auto lock = vector_guard.get_lock_guard(cur_group_idx); + + // Find the next vector that has data. + // If the current one does not, we move to the next (in the next iteration). + // If we looped around and arrive back where we started, we are done. + if( data[cur_group_idx].empty() ) { + ++cur_group_idx; + cur_group_idx %= num_vecs; + if( cur_group_idx == start_group_idx ) { + break; + } + continue; + } + + // Remove the last entry from the vector + // LOG_DBG << "thread " << t << " popping data[" << cur_group_idx << "] at " << (data[cur_group_idx].size() - 1); + data[cur_group_idx].pop_back(); + ++num_elem; + } + }); + } + thread_pool->wait_for_all_pending_tasks(); + + // Now we expect to have processed exactly the elements that we put in data, + // and that we have left the vectors in data empty. + EXPECT_EQ( exp_num_elem, num_elem ); + for( auto const& vec : data ) { + EXPECT_TRUE( vec.empty() ); + EXPECT_EQ( 0, vec.size() ); + } +}