Integer Operations and Threads

  • Integer operations are usually a single instruction

    • True on x64
    • Provided the data is correctly aligned and fits into a single word
  • A thread cannot be interrupted while performing integer operations

  • Do we still need to lock a shared integer?


#include <thread>
#include <iostream>
#include <vector>

int counter = 0;

void task() {
  for (int i = 0; i < 100'000; ++i) {
    ++counter;
  }
}

int main() {
  std::vector<std::thread> tasks;
  
  for (int i = 0; i < 10; ++i) {
    tasks.push_back(std::thread(task));
  }

  for (auto &thr : tasks)
    thr.join();

  std::cout << counter << '\n';
}

Integer Operation Discussion

  • The output is a number like 258413

    • Data race
  • The ++ operation is a single instruction

  • However, ++count involves three operations

    • Pre-fetch the value of the count
    • Increment the value in the processor core's register
    • Publish the new value of count
  • The thread could use a stale value in its calculation

  • The thread could publish its result after a thread which ran later

Thread Synchronization

  • We need to make sure that

    • Thread B uses the latest value for count
    • Thread A published its result immediately
  • A mutex does this internally when we call lock() and unlock()

  • Here, we can do this by declaring count as "atomic"

Atomic Keyword

  • The compiler will generate special instructions which

    • Disable pre-fetch for count
    • Flush the store buffer immediately after doing the increment
  • This also avoids some other problems

    • Hardware optimizations which change the instruction order
    • Compiler optimizations which change the instruction order
  • The result is that only one thread can access count at a time

  • This prevents the data race

    • It also makes the operation take much longer