HEKTO
HEKTO

Reputation: 4191

Stopping a thread using an `atomic_flag` variable

This is related to the second answer to this question.

My test code is below. I'm trying to launch a thread, and then to make it stop using the std::atomic_flag. Then the thread should output a number of loop executions and total duration, and stop.

std::atomic_flag keepRunning = ATOMIC_FLAG_INIT;

void F()
{
  keepRunning.test_and_set();
  long long unsigned count = 0;
  const time_t start = time(nullptr);
  while (keepRunning.test_and_set())
  {
    std::cout << '.';
    ++count;
  }
  const time_t duration = time(nullptr) - start;
  std::cout << count << '\t' << duration << std::endl;
}

int main()
{
  std::thread t(F);
  keepRunning.clear();
  t.join();
}

The problem is that the thread doesn't stop.

Why is that?

Upvotes: 1

Views: 3868

Answers (2)

NonNumeric
NonNumeric

Reputation: 1109

In your F() you ignore the output from the first keepRunning.test_and_set(). Your attempt to initialize the flag there causes a race with the keepRunning.clear() statement in main(). Depending on which of these statements runs first you either get the intended behavior or have ignored clear() call and a never ending thread.

By the time F() had a chance to run, the flag should be already initialized with the correct value. Moving that initial test_and_set() into main() prevents the race:

std::atomic_flag keepRunning = ATOMIC_FLAG_INIT;

void F()
{
  long long unsigned count = 0;
  const time_t start = time(nullptr);
  while (keepRunning.test_and_set())
  {
    std::cout << '.';
    ++count;
  }
  const time_t duration = time(nullptr) - start;
  std::cout << count << '\t' << duration << std::endl;
}

int main()
{
  keepRunning.test_and_set();
  std::thread t(F);
  keepRunning.clear();
  t.join();
}

Now the flag is effectively only changed in main() and only "read" in F().

Upvotes: 4

LWimsey
LWimsey

Reputation: 6677

It does not stop (most of the time) because the loop in F will probably never see a cleared flag.

Assuming that the creation of a thread will take some time, keepRunning.clear() in main is likely to run first. When F finally gets to run, it immediately sets the value and enters a loop that will never see a cleared flag and therefore never quits.

Instead of initially setting the flag value in F, a solution is to initialize it to true. However, std::atomic_flag does not let you do that because of its limited interface (this design is on purpose,std::atomic_flag is supposed to be used as a low-level building block for other primitives).

You could use a std::atomic<bool>, initialize it to true and remove the initial store(true) in F. For demo purposes, I added a sleep_for statement before clearing the flag in main.

std::atomic<bool> keepRunning{true};

void F()
{
  long long unsigned count = 0;
  const time_t start = time(nullptr);
  while (keepRunning)
  {
    std::cout << '.';
    ++count;
  }
  const time_t duration = time(nullptr) - start;
  std::cout << count << '\t' << duration << std::endl;
}

int main()
{
  std::thread t(F);
  std::this_thread::sleep_for(1s); // optional
  keepRunning = false;
  t.join();
}

Upvotes: 2

Related Questions