0

I have the following singleton class:

class RecordKeeper {
public:
  ...
  RecordKeeper* get();
  void addRecord(std::string &rec);
private:
  std::string records[MAX]; // this has to be a fixed size circular array
  int len;
};

void RecordKeeper::addRecord(std::string rec) {
  ...
  records[len++] = rec;
  ...
}


I want to do the following from multiple threads:

RecordKeeper::get()->addRecord(str);

Can I do it as an atomic operation using std::atomic? Currently I am using std::mutex and lock_guard like std::lock_guard<std::mutex> lg(lock). But is there a better way that will be more efficient and faster?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Sajib
  • 404
  • 3
  • 15
  • 1
    Without more information about what your current setup is actually doing, it's hard to say whether an alternative would suit your needs. What all is being protected with `lock`? What are other threads trying to do with `x`, `str`, and `S`? – Nathan Pierson May 12 '21 at 04:21
  • Thread safety is a relationship between two or more algorithms, not a property of data or even an algorithm. Atomicity is a kind of thread safety; but even it is relational. Also, you hopefully mean "lock free" – Yakk - Adam Nevraumont May 12 '21 at 04:38
  • 1
    There are three expressions. `S[x]`, `= str`, `x++`. What do you want to execute atomically? all of them? – 김선달 May 12 '21 at 05:19
  • @NathanPierson I have added some context to my code. – Sajib May 12 '21 at 05:54
  • @all yes I want to make it lock free as using mutex locks and lock_guard takes some time and I want to make it faster. – Sajib May 12 '21 at 05:55
  • 1
    @Sajib This talk might be helpful: [CppCon 2017: Fedor Pikus “C++ atomics, from basic to advanced. What do they really do?”](https://www.youtube.com/watch?v=ZQFzMfHIxng) – KIIV May 12 '21 at 06:04
  • 3
    If you want to achieve best speed-up, let every thread fill its own container of records and collect the results when you're done. If this is not an option I'm afraid you have to use a lock. From time to time, I read about lock free lists but 1) it's often debated whether they are really correct implemented 2) most times a `mutex` doesn't hurt that much if it's used with care. – Scheff's Cat May 12 '21 at 06:06
  • @Yakk-AdamNevraumont: To be fair, if you can get the hardware to do something as a single operation that's atomic from the PoV of all observers (other CPUs and DMA), it's not really relational. That's the gold standard, what std::atomic is intended to expose. But since hardware support for atomically doing something to two disjoint memory locations is very limited (e.g. m68k [DCAS](https://en.wikipedia.org/wiki/Double_compare-and-swap), x86 transactional mem), std::atomic doesn't expose such an operation. It would make every type have `lock_free() = false` except on those rare platforms :/ – Peter Cordes May 12 '21 at 15:38
  • Oh, and this wants to assign a `std::string`, not a simple type that can be lock-free on its own. Sounds like borrowing some ideas from a lock-free queue would be a good idea, to let you indicate somehow that a slot is finished being written, other than taking a lock that covers every element and the counters. – Peter Cordes May 12 '21 at 15:41
  • @PeterCordes Sure, but usually that ends up being "lock all CPUs" "do sequence of operations" "unlock all CPUs" which is like a `mutex`. Maybe you get away with "actually only do the locking if the hardware detects there is a conflict, maybe rolling back speculative execution, and ..." - modern and near future concurrency is not that far from the edge of relativistic limits on simultaneity, and is often on the edge or past the electricity speed limit. Atomic ends up being "hardware micro mutex", and making it less micro doesn't make it better. – Yakk - Adam Nevraumont May 12 '21 at 18:16
  • @Yakk-AdamNevraumont: x86 TSX hardware transactional memory aborts the transaction if another core (or DMA) does anything that conflicts with the current transaction in progress, apparently implemented using some extra bookkeeping in L1d cache to commit or discard a transaction. https://www.realworldtech.com/haswell-tm/ So it's useful for low-contention situations, but would livelock in high contention if you didn't fall back to actual locking. The mechanism is that `xbegin`takes an address to jump to on abort; [example](https://stackoverflow.com/a/36899866/224132) – Peter Cordes May 12 '21 at 18:24

0 Answers0