Nermal Skywalker
Nermal Skywalker

Reputation: 49

When creating threads using lambda expressions, how to give each thread its own copy of the lambda expression?

I have been working on a program that basically used brute force to work backward to find a method using a given set of operations to reach the given number. So, for example, if I gave in a set of operations +5,-7,*10,/3, and a given number say 100(*this example probably won't come up with a solution), and also a given max amount of moves to solve (let's say 8), it will attempt to come up with a use of these operations to get to 100. This part works using a single thread which I have tested in an application.

However, I wanted it to be faster and I came to multithreading. I have worked a long time to even get the lambda function to work, and after some serious debugging have realized that the solution "combo" is technically found. However, before it is tested, it is changed. I wasn't sure how this was possible considering the fact that I had thought that each thread was given its own copy of the lambda function and its variables to use.

In summary, the program starts off by parsing the information, then passes the information which is divided by the parser as paramaters into the array of an operation object(somewhat of a functor). It then uses an algorithm which generated combinations which are then executed by the operation objects. The algorithm, in simplicity, takes in the amount of operations, assigns it to a char value(each char value corresponds to an operation), then outputs a char value. It generates all possible combinations.

That is a summary of how my program works. Everything seems to be working fine and in order other than two things. There is another error which I have not added to the title because there is a way to fix it, but I am curious about alternatives. This way is also probably not good for my computer.

So, going back to the problem with the lambda expression inputted with the thread as seen is with what I saw using breakpoints in the debugger. It appeared that both threads were not generating individual combos, but more rather properly switching between the first number, but alternating combos. So, it would go 1111, 2211, rather than generating 1111, 2111.(these are generated as the previous paragraph showed, but they are done a char at a time, combined using a stringstream), but once they got out of the loop that filled the combo up, combos would get lost. It would randomly switch between the two and never test the correct combo because combinations seemed to get scrambled randomly. This I realized must have something to do with race conditions and mutual exclusion. I had thought I had avoided it all by not changing any variables changed from outside the lambda expression, but it appears like both threads are using the same lambda expression.

I want to know why this occurs, and how to make it so that I can say create an array of these expressions and assign each thread its own, or something similar to that which avoids having to deal with mutual exclusion as a whole.

Now, the other problem happens when I at the end delete my array of operation objects. The code which assigns them and the deleting code is shown below.

  operation *operations[get<0>(functions)];

  for (int i = 0; i < get<0>(functions); i++)
  {
        //creates a new object for each operation in the array and sets it to the corresponding parameter
        operations[i] = new operation(parameterStrings[i]);
  }
  delete[] operations;

The get<0>(functions) is where the amount of functions is stored in a tuple and is the number of objects to be stored in an array. The paramterStrings is a vector in which the strings used as parameters for the constructor of the class are stored. This code results in an "Exception trace/breakpoint trap." If I use "*operations" instead I get a segmentation fault in the file where the class is defined, the first line where it says "class operation." The alternative is just to comment out the delete part, but I am pretty sure that it would be a bad idea to do so, considering the fact that it is created using the "new" operator and might cause memory leaks.

Below is the code for the lambda expression and where the corresponding code for the creation of threads. I readded code inside the lambda expression so it could be looked into to find possible causes for race conditions.

auto threadLambda = [&](int thread, char *letters, operation **operations, int beginNumber) {
int i, entry[len];
        bool successfulComboFound = false;
        stringstream output;
        int outputNum;

        for (i = 0; i < len; i++)
        {
              entry[i] = 0;
        }
        do
        {
              for (i = 0; i < len; i++)
              {
                    if (i == 0)
                    {
                          output << beginNumber;
                    }

                    char numSelect = *letters + (entry[i]);

                    output << numSelect;
              }
              outputNum = stoll(output.str());
              if (outputNum == 23513511)
              {
                    cout << "strange";
              }
              if (outputNum != 0)
              {
                    tuple<int, bool> outputTuple;
                    int previousValue = initValue;
                    for (int g = 0; g <= (output.str()).length(); g++)
                    {
                          operation *copyOfOperation = (operations[((int)(output.str()[g])) - 49]);

                          //cout << copyOfOperation->inputtedValue;

                          outputTuple = (*operations)->doOperation(previousValue);
                          previousValue = get<0>(outputTuple);

                          if (get<1>(outputTuple) == false)
                          {
                                break;
                          }
                          debugCheck[thread - 1] = debugCheck[thread - 1] + 1;
                          if (previousValue == goalValue)
                          {
                                movesToSolve = g + 1;
                                winCombo = outputNum;
                                successfulComboFound = true;
                                break;
                          }
                    }
                    //cout << output.str() << ' ';
              }
              if (successfulComboFound == true)
              {
                    break;
              }
              output.str("0");

              for (i = 0; i < len && ++entry[i] == nbletters; i++)
                    entry[i] = 0;
        } while (i < len);

        if (successfulComboFound == true)
        {
              comboFoundGlobal = true;
              finishedThreads.push_back(true);
        }
        else
        {
              finishedThreads.push_back(true);
        }
  };

Threads created here :

  thread *threadArray[numberOfThreads];

  for (int f = 0; f < numberOfThreads; f++)
  {
        threadArray[f] = new thread(threadLambda, f + 1, lettersPointer, operationsPointer, ((int)(workingBeginOperations[f])) - 48);
  }

If any more of the code is needed to help solve the problem, please let me know and I will edit the post to add the code. Thanks in advance for all of your help.

Upvotes: 1

Views: 2200

Answers (1)

BeeOnRope
BeeOnRope

Reputation: 65046

Your lambda object captures its arguments by reference [&], so each copy of the lambda used by a thread references the same shared objects, and so various threads race and clobber each other.

This is assuming things like movesToSolve and winCombo come from captures (it is not clear from the code, but it seems like it). winCombo is updated when a successful result is found, but another thread might immediately overwrite it right after.

So every thread is using the same data, data races abound.

You want to ensure that your lambda works only on two three types of data:

  1. Private data
  2. Shared, constant data
  3. Properly synchronized mutable shared data

Generally you want to have almost everything in category 1 and 2, with as little as possible in category 3.

Category 1 is the easiest, since you can use e.g., local variables within the lambda function, or captured-by-value variables if you ensure a different lambda instance is passed to each thread.

For category 2, you can use const to ensure the relevant data isn't modified.

Finally you may need some shared global state, e.g., to indicate that a value is found. One option would be something like a single std::atomic<Result *> where when any thread finds a result, they create a new Result object and atomically compare-and-swap it into the globally visible result pointer. Other threads check this pointer for null in their run loop to see if they should bail out early (I assume that's what you want: for all threads to finish if any thread finds a result).

A more idiomatic way would be to use std::promise.

Upvotes: 1

Related Questions