views:

626

answers:

7

I have a multithreaded application that runs using a custom thread pool class. The threads all execute the same function, with different parameters.

These parameters are given to the thread pool class the following way :

// jobParams is a struct of int, double, etc...
jobParams* params = new jobParams;
params.value1 = 2;
params.value2 = 3;

int jobId = 0;

threadPool.addJob(jobId, params);

As soon as a thread has nothing to do, it gets the next parameters and runs the job function. I decided to take care of the deletion of the parameters in the thread pool class :

ThreadPool::~ThreadPool() {
    for (int i = 0; i < this->jobs.size(); ++i) {
        delete this->jobs[i].params;
    }
}

However, when doing so, I sometimes get a heap corruption error :

Invalid Address specified to RtlFreeHeap

The strange thing is that in one case it works perfectly, but in another program it crashes with this error. I tried deleting the pointer at other places : in the thread after the execution of the job function (I get the same heap corruption error) or at the end of the job function itself (no error in this case).

I don't understand how deleting the same pointers (I checked, the addresses are the same) from different places changes anything. Does this have anything to do with the fact that it's multithreaded ?

UPDATE: I do have a critical section that handles the access to the parameters. I don't think the problem is about synchronized access. Anyway, the destructor is called only once all threads are done, and I don't delete any pointer anywhere else. Can pointer be deleted automatically ?

UPDATE2: as requested, some code ! The list of jobs is a queue of a structure, composed of the id of a job (used to be able to get the output of a specific job later) and the parameters.

getNextJob() is called by the threads (they have a pointer to the ThreadPool) each time they finished to execute their last job.

void ThreadPool::addJob(int jobId, void* params) {
    jobData job; // jobData is a simple struct { int, void* }
    job.ID = jobId;
    job.params = params;

    // insert parameters in the list
    this->jobs.push(job);
}

jobData* ThreadPool::getNextJob() {    
    // get the data of the next job
    jobData* job = NULL;

    // we don't want to start a same job twice,
    // so we make sure that we are only one at a time in this part
    WaitForSingleObject(this->mutex, INFINITE);

    if (!this->jobs.empty())
    {
     job = &(this->jobs.front());
     this->jobs.pop();
    }

    // we're done with the exclusive part !
    ReleaseMutex(this->mutex);

    return job;
}
+1  A: 

All access to the job queue must be synchronized, i.e. performed only from 1 thread at a time by locking the job queue prior to access. Do you already have a critical section or some similar pattern to guard the shared resource? Synchronization issues often lead to weird behaviour and bugs which are hard to reproduce.

Nikola Gedelovski
+1  A: 

It's hard to give a definitive answer with this amount of code. But generally speaking, multithreaded programming is all about synchronizing access to data that might be accessed from multiple threads. If there is no long or other synchronization primitive protecting access to the threadpool class itself, then you can potentially have multiple threads reaching your deletion loop at the same time, at which point you're pretty much guaranteed to be double-freeing memory.

The reason you're getting no crash when you delete a job's params at the end of the job function might be because access to a single job's params is already implicitly serialized by your work queue. Or you might just be getting lucky. In either case, it's best to think about locks and synchronization primitive as not being something that protects code, but as being something that protects data (I've always thought the term "critical section" was a bit misleading here, as it tends to lead people to think of a 'section of lines of code' rather than in terms of data access).. In this case, since you want to access your jobs data from multiple thread, you need to be protecting it via a lock or some other synchronization primitive.

peterb
Only the main threads has an instance of the ThreadPool class, so the destructor will only be called once. The access to the parameters is handled by a mutex so no danger here.
Wookai
+1  A: 

If you try to delete an object twice, the second time will fail, because the heap is already freed. This is the normal behavior.

Now, since you are in a multithreading context... it might be that the deletions are done "almost" in parallel, which might avoid the error on the second deletion, because the first one is not yet finalized.

Cătălin Pitiș
+5  A: 

Let's turn this on its head: Why are you using pointers at all?

class Params
{
int value1, value2; // etc...
}

class ThreadJob
{
  int jobID;  // or whatever...
  Params params;
}

class ThreadPool
{
  std::list<ThreadJob> jobs;

  void addJob(int job, const Params & p)
  {
     ThreadJob j(job, p);
     jobs.push_back(j);
  }
}

No new, delete or pointers... Obviously some of the implementation details may be cocked, but you get the overall picture.

Roddy
I use pointers to be able to have any type of parameters for my function. This ThreadPool class is generic, and thus should not be bound to a specific type of parameter.
Wookai
One word: TEMPLATES!
Roddy
Hum. Good point ;) ! I also used pointers for performance, but I guess I could simply pass them by reference...
Wookai
@Roddy Templates will not solve this problem if he wants run-time polymorphism for his parameters.
anon
@Neil, True, but in that case his "Params" class can contain a void * ...
Roddy
@Wookai, careless use of references, and "pointers for performance" is what got you into this mess in the first place. Scott Meyers, (Effective C++) writes "Unrelenting in their pursuit of pass-by-reference purity, they invariably make a fatal mistake: They start to pass pointers to objects that don't exist"
Roddy
Templates can solve the problem, boost.Function - boost.Lambda - boost.Any - boost.Variant are very helpful tools for this
TimW
@Roddy: wrapping the pointer in a class does not change much : I will still have to delete its content, and will get the same bug. Maybe I'm careless, but I find the original design pretty simple and efficient, and would like to keep it that way.@TimW: unfortunately I can't use boost on this project.
Wookai
@Wookai. Here's what I'd do: worry about making it 'generic' once you've got the basics working. Get a threadpool working with a 'fixed' param structure, with copy-by-value everywhere. No pointers, no references, no new or delete. Once that's working, make a templated one for generic param classes. (Don't do templates as the first step, as debugging them is a nightmare). Then - and ONLY then - worry about efficiency.
Roddy
@Wookai - regards wrapping the pointer in a class: That was a tongue-in-cheek suggestion, because I don't see that you need to complicate further trying to support some kind of run-time polymorphism.
Roddy
@Roddy: starting again from something simpler is a good idea. Thanks for all your suggestions !
Wookai
+4  A: 

Thanks for extra code. Now we can see a problem -

in getNextJob

if (!this->jobs.empty())
{
    job = &(this->jobs.front());
    this->jobs.pop();

After the "pop", the memory pointed to by 'job' is undefined. Don't use a reference, copy the actual data!

Try something like this (it's still generic, because JobData is generic):

jobData ThreadPool::getNextJob()    // get the data of the next job
{
  jobData job;

  WaitForSingleObject(this->mutex, INFINITE);

  if (!this->jobs.empty())
  {
    job = (this->jobs.front());
    this->jobs.pop();
  }

  // we're done with the exclusive part !
  ReleaseMutex(this->mutex);

  return job;

}

Also, while you're adding jobs to the queue you must ALSO lock the mutex, to prevent list corruption. AFAIK std::lists are NOT inherently thread-safe...?

Roddy
In fact, this code is what I got after trying to fix the problem. At first, I used a vector instead of a queue, and thus did not erase the element of the list. The problem was the same.
Wookai
Calling pop() calls the destructor of the queued object. Accessing it through the "job" reference is definitely going to cause problems, as is deleting it again in ~ThreadPool().http://www.cplusplus.com/reference/stl/queue/pop/
Mark Bessey
+1  A: 

Use smart pointers or other RAII to handle your memory.


If you have access to boost or tr1 lib you can do something like this.

class ThreadPool
{
    typedef pair<int, function<void (void)> > Job;
    list< Job > jobList;
    HANDLE mutex;

public:
    void addJob(int jobid, const function<void (void)>& job) {
        jobList.push_back( make_pair(jobid, job) );
    }

    Job getNextJob() {    

        struct MutexLocker {
            HANDLE& mutex;
            MutexLocker(HANDLE& mutex) : mutex(mutex){ 
                WaitForSingleObject(mutex, INFINITE); 
            }
            ~MutexLocker() { 
                ReleaseMutex(mutex); 
            }
        };

        Job job = make_pair(-1, function<void (void)>());
        const MutexLocker locker(this->mutex);
        if (!this->jobList.empty()) {
            job = this->jobList.front();
            this->jobList.pop();
        }
        return job;
    }
};


void workWithDouble( double value );
void workWithInt( int value );
void workWithValues( int, double);

void test() {
    ThreadPool pool;
    //...
    pool.addJob( 0, bind(&workWithDouble, 0.1));
    pool.addJob( 1, bind(&workWithInt, 1));
    pool.addJob( 2, bind(&workWithValues, 1, 0.1));
}
TimW
I heard about that but never used it. Using smart pointers would allow me not to worry about deleting them, right ?
Wookai
The shared_ptr class template stores a pointer to a dynamically allocated object, typically with a C++ new-expression. The object pointed to is guaranteed to be deleted when the last shared_ptr pointing to it is destroyed or reset
TimW
Thanks for the example. I can't use either libs but I got the point !
Wookai
+1  A: 

Using operator delete on pointer to void results in undefined behavior according to the specification.

Chapter 5.3.5 of the draft of the C++ specification. Paragraph 3.

In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.73)

And corresponding footnote.

This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void

Komat