0

I have a class called "Vector", which by default holds 10.000 elements, which at all times must have the same value. This class is tested and works. Therefore I use the method setAndTest() from the class to set the value of all elements, which then immediately checks whether the Vector object is consistent (that all vector elements hold the same value).

In a new file "main.cpp", i have created two functions: writer() and main(). writer() creates a user-defined number of writer threads (between 1 & 100), each with their own unique id. Each writer sets and tests the shared Vector object to its id every second. If a writer detects an inconsistensy in a shared Vector object, setAndTest() returns false and the following error message should be printed: Error with thread #id However, in 99% of the cases it prints Success with thread #id, whereas I expected that there would be more variation between the two.

Headers included in main.cpp file:

#include <iostream>
#include "Vector.hpp"
#include <pthread.h>
#include <unistd.h>
using namespace std;

Vector object and writer() function:

Vector VecObj;  //The Vector object (Defined in global scope)

void* writer(void *threadid)
{
    int threadid_ = *(int *)(threadid); 

    if(VecObj.setAndTest(threadid_))
    {
        std::cout << "\nSuccess with thread " << threadid_ << endl;
    }else
    {
        std::cout << "\nError with thread " << threadid_ << endl;
    }

    return NULL;
}

main function:

int main()
{
    start:
    int numOfThreads = 1;
    std::cout << "Enter amount of threads (must be between 1 & 100): ";
    std::cin >> numOfThreads;
    if(0 < numOfThreads && numOfThreads <= 100){
        std::cout << "You entered " << numOfThreads << " threads" << endl;
    }else{
        std::cout << "Amount of threads must be between 1 & 100" << endl;
        goto start;
    }

    pthread_t threadcreator[numOfThreads];

    for(int i = 0; i < numOfThreads; i++){
        pthread_create(&threadcreator[i], NULL, writer, &i);
        sleep(1);
    }

    for(int i = 0; i < numOfThreads; i++){
        pthread_join(threadcreator[i], NULL);
    }

}

Vector Class (Vector.hpp):

#ifndef VECTOR_HPP_
#define VECTOR_HPP_
#include <pthread.h>
using namespace std;

//=======================================================
// Class: Vector
// contains a size_-size vector of integers.
// Use the function setAndTest to set all elements
// of the vector to a certain value and then test that
// the value is indeed correctly set
//=======================================================
class Vector
{
public:
   Vector(unsigned int size = 10000) : size_(size)
      {
         vector_ = new int[size_];
         set(0);
      }

   ~Vector()
      {
         delete[] vector_;
      }

   bool setAndTest(int n)
      {
         set(n);
         return test(n);
      }

private:
   void set(int n)
      {
         for(unsigned int i=0; i<size_; i++) vector_[i] = n;
      }

   bool test(int n)
      {
         for(unsigned int i=0; i<size_; i++) if(vector_[i] != n) return false;
         return true;
      }

   int*           vector_;
   unsigned int   size_;

};

#endif

  • "*This class is tested and works.*": The class clearly violates the [rule-of-three](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). (Although it doesn't matter for this specific case, as far as I can tell.) – walnut Feb 25 '20 at 01:56
  • Is there any reason you are not using the C++11 threading facilities from ``? – walnut Feb 25 '20 at 01:59

1 Answers1

2

You are passing each thread a pointer to the same int variable. That variable changes value on each loop iteration. writer() is expecting to receive the same int value that was given to pthread_create(), but that is not guaranteed in your code, even with the sleep() call.

To pass the int correctly, pass the actual int value itself rather than a pointer to the int, eg:

#include <iostream>
#include <vector>
#include <cstdint>
#include <pthread.h>
#include "Vector.hpp"

Vector VecObj;

void* writer(void *arg)
{
    int threadid_ = static_cast<int>(reinterpret_cast<intptr_t>(arg));

    if (VecObj.setAndTest(threadid_))
    {
        std::cout << "\nSuccess with thread " << threadid_ << std::endl;
    }
    else
    {
        std::cout << "\nError with thread " << threadid_ << std::endl;
    }

    return NULL;
}

int main()
{
    int numOfThreads = 0;

    do {
        std::cout << "Enter amount of threads (must be between 1 & 100): ";
        std::cin >> numOfThreads;
        if (0 < numOfThreads && numOfThreads <= 100){
            std::cout << "You entered " << numOfThreads << " threads" << std::endl;
            break;
        }
        std::cout << "Amount of threads must be between 1 & 100" << std::endl;
    }
    while (true);

    std::vector<pthread_t> threadcreator(numOfThreads);

    for(int i = 0; i < numOfThreads; i++){
        pthread_create(&threadcreator[i], NULL, writer, reinterpret_cast<void*>(i));
    }

    for(int i = 0; i < numOfThreads; i++){
        pthread_join(threadcreator[i], NULL);
    }

    return 0;
}

If you really want to use int* pointers, then you will have to allocate a separate int for each thread, eg:

#include <iostream>
#include <vector>
#include <pthread.h>
#include "Vector.hpp"

Vector VecObj;

void* writer(void *arg)
{
    int threadid_ = *static_cast<int*>(arg);

    if (VecObj.setAndTest(threadid_))
    {
        std::cout << "\nSuccess with thread " << threadid_ << std::endl;
    }
    else
    {
        std::cout << "\nError with thread " << threadid_ << std::endl;
    }

    return NULL;
}

int main()
{
    int numOfThreads = 0;

    do {
        std::cout << "Enter amount of threads (must be between 1 & 100): ";
        std::cin >> numOfThreads;
        if (0 < numOfThreads && numOfThreads <= 100){
            std::cout << "You entered " << numOfThreads << " threads" << std::endl;
            break;
        }
        std::cout << "Amount of threads must be between 1 & 100" << std::endl;
    }
    while (true);

    std::vector<pthread_t> threadcreator(numOfThreads);
    std::vector<int> threadids(numOfThreads);

    for(int i = 0; i < numOfThreads; i++){
        threadids[i] = i;
        pthread_create(&threadcreator[i], NULL, writer, &threadids[i]);
    }

    for(int i = 0; i < numOfThreads; i++){
        pthread_join(threadcreator[i], NULL);
    }

    return 0;
}

Or, if you really want to pass an int* pointer to a single int, use a std::conditional_variable or other waitable signal to make sure that each thread has actually captured the int value before allowing the loop to change its value, eg:

#include <iostream>
#include <vector>
#include <conditional_variable>
#include <mutex>
#include "Vector.hpp"
#include <pthread.h>

Vector VecObj;
std::condition_variable cv;
std::mutex cv_m;
bool captured = false;

void* writer(void *arg)
{
    int threadid_;

    {
    std::lock_guard<std::mutex> lk(cv_m);
    threadid_ = *static_cast<int*>(arg);
    captured = true;
    }
    cv.notify_one();

    if (VecObj.setAndTest(threadid_))
    {
        std::cout << "\nSuccess with thread " << threadid_ << std::endl;
    }
    else
    {
        std::cout << "\nError with thread " << threadid_ << std::endl;
    }

    return NULL;
}

int main()
{
    int numOfThreads = 0;

    do {
        std::cout << "Enter amount of threads (must be between 1 & 100): ";
        std::cin >> numOfThreads;
        if (0 < numOfThreads && numOfThreads <= 100){
            std::cout << "You entered " << numOfThreads << " threads" << std::endl;
            break;
        }
        std::cout << "Amount of threads must be between 1 & 100" << std::endl;
    }
    while (true);

    std::vector<pthread_t> threadcreator(numOfThreads);

    for(int i = 0; i < numOfThreads; i++){
        std::unique_lock<std::mutex> lk(cv_m);
        captured = false;
        pthread_create(&threadcreator[i], NULL, writer, &i);
        cv.wait(lk, [](){ return captured; });
    }

    for(int i = 0; i < numOfThreads; i++){
        pthread_join(threadcreator[i], NULL);
    }

    return 0;
}

UPDATE: oh, now I see another major problem. You have multiple threads writing to, and reading from, a single Vector object in memory without synchronization. That is not safe to do. While one thread is reading from an element in the Vector's array, another thread can be writing a new value to that same element, and there is no guarantee that the element will remain consistent across both operations. You MUST synchronize access to the Vector object since it is being shared across multiple threads, eg:

...
#include <mutex>
...

Vector VecObj;
std::mutex vec_m;
...

void* writer(void *threadid)
{
    int threadid_ = ...;
    bool testResult;

    {
    std::lock_guard lk(vec_m);
    testResult = VecObj.setAndTest(threadid_);
    }

    if (testResult)
    {
        std::cout << "\nSuccess with thread " << threadid_ << std::endl;
    }
    else
    {
        std::cout << "\nError with thread " << threadid_ << std::endl;
    }

    return NULL;
}

...
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • That's not really helping. It's just giving me a whole bunch of errors. – user54952820 Feb 25 '20 at 01:32
  • Also, it doesn't make sense to `#include ` inside my main.cpp file, since Vector.hpp is included in main.cpp. – user54952820 Feb 25 '20 at 01:33
  • @user54952820 "*It's just giving me a whole bunch of errors*" - you are going to have to be more specific than that. What are the actual errors? – Remy Lebeau Feb 25 '20 at 01:42
  • @user54952820 `` defines the standard `std::vector` container, whereas `Vector.hpp` defines your custom `Vector` class. Two separate classes. It doesn't matter if `Vector` uses `std::vector` internally or not (which it doesn't). `main()` wants to use `std::vector` so it should `#include `. If `` has already been `#include`'d before, that is perfectly OK – Remy Lebeau Feb 25 '20 at 01:43
  • I just don't understand your use of `std::vector` in this situation. – user54952820 Feb 25 '20 at 01:46
  • It is just a means of allocating a variable-length array at runtime. The way you are doing it is not standard C++ and is supported as an extension by only a few compilers. – Remy Lebeau Feb 25 '20 at 01:47
  • If I try your first example where you use `int threadid_ = reinterpret_cast(threadid);` instead of `int threadid_ = *(int *)(threadid);`, I get an error message that says: `"cast from ‘void*’ to ‘int’ loses precision"` – user54952820 Feb 25 '20 at 01:55
  • @user54952820 see [error: cast from 'void*' to 'int' loses precision](https://stackoverflow.com/questions/1640423/). You can use `intptr_t` instead of `int` for the cast inside of `writer()`. I have updated my answer to show that. – Remy Lebeau Feb 25 '20 at 01:57