0

I'm trying to turn a code from a single thread to a multi thread(example, create 6 threads instead of 1) while making sure they all start and finish without any interference from each other. What would be a way to do this? Could I just do a for loop that creates a thread until i < 6? And just add a mutex class with lock() and unlock()?

#include <iostream>
#include <boost/thread.hpp>
#include <boost/date_time.hpp>

void workerFunc()
{
    boost::posix_time::seconds workTime(3);

    std::cout << "Worker: running" << std::endl;

    // Pretend to do something useful...
    boost::this_thread::sleep(workTime);

    std::cout << "Worker: finished" << std::endl;
}

int main(int argc, char* argv[])
{
    std::cout << "main: startup" << std::endl;

    boost::thread workerThread(workerFunc);

    std::cout << "main: waiting for thread" << std::endl;

    workerThread.join();

    std::cout << "main: done" << std::endl;

    system("pause");
    return 0;
}
zxtron
  • 1
  • 2
  • you need a mutex to synchronize access to shared data, if the threads just pretend to do something useful you dont need a mutex. What happened when you tried to create threads in a loop? – 463035818_is_not_an_ai Jun 29 '19 at 21:22
  • Manually managing threads is not that efficient. A [thread pool](https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/reference/thread_pool.html) abstracts from threads and lets you focus on the work (tasks) instead. I. e. you just post tasks to the thread pool which dispatches it to threads that are/become idle. – zett42 Jun 29 '19 at 22:57

3 Answers3

2

Yes, it's certainly possible. Since you don't want any interference between them, give them unique data to work with so that you do not need to synchronize the access to that data with a std::mutex or making it std::atomic. To further minimize the interference between threads, align the data according to std::hardware_destructive_interference_size.

You can use boost::thread::hardware_concurrency() to get the number of hardware threads available on the current system so that you don't have to hardcode the number of threads to run.

Passing references to the thread can be done using std::ref (or else the thread will get a ref to a copy of the data).

Here I create a std::list of threads and a std::vector of data to work on.

#include <cstdint> // std::int64_t
#include <iostream>
#include <list>
#include <new> // std::hardware_destructive_interference_size
#include <vector>
#include <boost/thread.hpp>

unsigned hardware_concurrency() {
    unsigned rv = boost::thread::hardware_concurrency();
    if(rv == 0) rv = 1; // fallback if hardware_concurrency returned 0
    return rv;
}

// if you don't have hardware_destructive_interference_size, use something like this
// instead:
//struct alignas(64) data {
struct alignas(std::hardware_destructive_interference_size) data {
    std::int64_t x;
};

void workerFunc(data& d) {
    // work on the supplied data
    for(int i = 0; i < 1024*1024-1; ++i) d.x -= i;
    for(int i = 0; i < 1024*1024*1024-1; ++i) d.x += i;
}

int main() {
    std::cout << "main: startup" << std::endl;

    size_t number_of_threads = hardware_concurrency();
    std::list<boost::thread> threads;
    std::vector<data> dataset(number_of_threads);

    // create the threads 
    for(size_t idx = 0; idx < number_of_threads; ++idx)
        threads.emplace_back(workerFunc, std::ref(dataset[idx]));

    std::cout << "main: waiting for threads" << std::endl;

    // join all threads
    for(auto& th : threads) th.join();
    // display results
    for(const data& d : dataset) std::cout << d.x << "\n";

    std::cout << "main: done" << std::endl;
}

If you are using C++11 (or later), I suggest using std::thread instead.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
1

Starting and stopping a bunch of Boost threads

std::vector<boost::thread> threads;
for (int i = 0; i < numberOfThreads; ++i) {
  boost::thread t(workerFunc);
  threads.push_back(std::move(t));
}

for (auto& t : threads) {
  t.join();
}

Keep in mind that join() doesn't terminate the threads, it only waits until they are finished.

Synchronization

Mutexes are required if multiple threads access the same data and at least one of them is writing the data. You can use a mutex to ensure that multiple threads enter the critical sections of the code. Example:

std::queue<int> q;
std::mutex q_mu;

void workerFunc1() {
  // ...
  {
    std::lock_guard<std::mutex> guard(q_mu);
    q.push(foo);
  } // lock guard goes out of scope and automatically unlocks q_mu
  // ...
}

void workerFunc2() {
  // ...
  {
    std::lock_guard<std::mutex> guard(q_mu);
    foo = q.pop();
  } // lock guard goes out of scope and automatically unlocks q_mu
  // ...
}

This prevents undefined behavior like reading an item from the queue that hasn't been written completely. Be careful - data races can crash your program or corrupt your data. I'm frequently using tools like Thread Sanitizer or Helgrind to ensure I didn't miss anything. If you only want to pass results back into the main program but don't need to share data between your threads you might want to consider using std::promise and std::future.

Martin Konrad
  • 1,075
  • 1
  • 10
  • 20
0

Yes, spawning new threads can be done with a simple loop. You will have to keep a few things in mind though:

  1. If threads will operate on shared data, it will need to be protected with mutexes, atomics or via some other way to avoid data races and undefined behaviour (bear in mind that even primitive types such as int have to be wrapped with an atomic or mutex according to the standard).
  2. You will have to make sure that you will eventually either call join() or detach() on every spawned thread before its object goes out of scope to prevent it from suddenly terminating.
  3. Its best to do some computations on the main thread while waiting for worker threads to use this time efficiently instead of wasting it.
  4. You generally want to spawn 1 thread less than the number of total threads you want as the program starts running with with one thread by default (the main thread).
  • Isn't detach() part of the standard library? I'm trying to pull this code off using boost functions. Would lock() and unlock() achieve the same concept? – zxtron Jun 29 '19 at 21:46
  • Boost can also detach threads. Note that `join()`/`detach()` are orthogonal to `lock()`/`unlock()`: Joining a thread means to wait until the thread function is finished (this is what most programs do). Detaching a thread means separating it from the thread object (rarely used). Locking on the other hand is used to prevent [data races](https://en.cppreference.com/w/cpp/language/memory_model) on data that is used by multiple threads with at least one of them writing the data. – Martin Konrad Jun 29 '19 at 22:22