Catree Catree - 21 days ago 8
C++ Question

Pthread using only one thread when encapsulated into a class

I don't understand why when "encapsulated" into a class, pthread use only one thread whereas it uses all the threads when using plain calls to pthread functions. There should be something wrong in the implementation.

See the following code:

#include <iostream>
#include <vector>
#include <pthread.h>
#include <sys/time.h>


namespace {
class Functor {
public:
Functor(const std::vector<int> &v) :
m_v(v), m_res() {
}

Functor() : m_v(), m_res() {
}

void operator()() {
computeImpl();
}

std::vector<int> getResult() const {
return m_res;
}

private:
std::vector<int> m_v;
std::vector<int> m_res;

void computeImpl() {
//Long computation (remove duplicates)
for (size_t i = 0; i < m_v.size(); i++) {
bool duplicate = false;

for (size_t j = 0; j < m_res.size() && !duplicate; j++) {
if (m_v[i] == m_res[j]) {
duplicate = true;
}
}

if (!duplicate) {
m_res.push_back(m_v[i]);
}
}
}
};

void * thread(void * args) {
Functor* f = reinterpret_cast<Functor*>(args);
(*f)();
return 0;
}

double measureTimeMs() {
struct timeval tp;
gettimeofday(&tp,0);
return(1000.0*tp.tv_sec + tp.tv_usec/1000.0);
}

class MyThread {
public:
MyThread(void * (*func)(void *), void *arg) : m_pid() {
pthread_create(&m_pid, NULL, func, arg);
}

MyThread() : m_pid() { }

virtual ~MyThread() {
join();
}

void join() {
pthread_join(m_pid, NULL);
}

private:
pthread_t m_pid;
};
}

int main() {
//Pthread encapsulated into a class
size_t nb_threads = 4;
std::vector<MyThread> threads(nb_threads);
std::vector<Functor> functors(nb_threads);

std::vector<int> v(100000);
srand(0);
for (size_t i = 0; i < v.size(); i++) {
v[i] = rand();
}

double t_thread = measureTimeMs();
for (size_t i = 0; i < nb_threads; i++) {
functors[i] = Functor(v);
threads[i] = MyThread(thread, &functors[i]);
}

for (size_t i = 0; i < nb_threads; i++) {
threads[i].join();
}
t_thread = measureTimeMs() - t_thread;
std::cout << "Pthread encapsulated into a classs" << std::endl;
std::cout << "t_thread=" << t_thread << " ms" << std::endl;


//Only Pthread
std::vector<pthread_t> pid(nb_threads);
t_thread = measureTimeMs();
for (size_t i = 0; i < nb_threads; i++) {
functors[i] = Functor(v);
pthread_create(&pid[i], NULL, thread, &functors[i]);
}

for (size_t i = 0; i < nb_threads; i++) {
pthread_join(pid[i], NULL);
}
t_thread = measureTimeMs() - t_thread;
std::cout << "Only Pthread" << std::endl;
std::cout << "t_thread=" << t_thread << " ms" << std::endl;

return EXIT_SUCCESS;
}


Output:


Pthread encapsulated into a classs

t_thread=4056.75 ms

Only Pthread

t_thread=2619.55 ms


My environment: Ubuntu 16.04 and I use System Monitor to check the CPU activity. In the first case (encapsulation), only one thread is at 100% whereas it uses 4 threads at 100% in the second case.

Also, my computer has 2 cores / 4 threads.

Answer

Your setup for your threads are introducing copies. Further, the sources of those copies are being destroyed synchronously as the threads are created (and therefore started, run, and joined before the next thread is started, etc.). And adding final salt into the wound, the join is being done twice as well.

Changing the setup:

std::vector<MyThread> threads;
std::vector<Functor> functors;

threads.reserve(nb_threads);
functors.reserve(nb_threads);

for (int i = 0; i < nb_threads; i++)
{
    functors.emplace_back(v);
    threads.emplace_back(thread, &functors[(size_t) i]);
}

// will fire all destructors, and consequently join.
threads.clear();

Note we don't fire the join method here. Your destructor already does that, and will be fired when the thread vector is clear()-ed. Further. we reserve space for the threads, and construct them in-place in the vector.

Running the above should get you the number similarity you seek.