0

Assume we use the standard consumer/producer pattern in our C++11 program: (from: http://en.cppreference.com/w/cpp/atomic/memory_order)

#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;
int data;

void producer()
{
    std::string* p = new std::string("Hello");
    ptr.store(p, std::memory_order_release);
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_consume)))
        ;
    assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr

    // yea well, it actually uses p2 for quite a while probably....
}

int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

Now, I would like to change the behavior of the producer code just a bit. Instead of simply setting a string, I'd like it to overwrite a string. E.g.:

void producer()
{
    std::string* p = new std::string("Hello");
    ptr.store(p, std::memory_order_release);

    // do some stuff

    std::string* p2 = new std::string("Sorry, should have been Hello World");
    ptr.store(p2, std::memory_order_release);

    // **
}

The producer here is responsible for the generation of the strings, which means that in my simple world it should also be responsible for the destruction of these strings.

In the line marked with '**' we should therefore destroy string 'p', which is what this question is about.

The solution you might consider would be to add (at the marked line):

delete p;

However, this will break the program, since the consumer might be using the string after we've deleted it -- after all, the consumer uses a pointer. Also, this implies that the producer waits for the consumer, which isn't necessary - we just want our old memory to be cleaned up. Using a ref counting smart pointer seems to be out of the question, since atomic only supports that many types.

What's the best (most efficient) way to fix this?

atlaste
  • 30,418
  • 3
  • 57
  • 87

1 Answers1

2

You can do an atomic exchange, which will return the previous value of the atomic variable.

The variable ptr then has two states: It can either have no data available, in which case it is equal to nullptr, or have data available for consumption.

To consume data any of the consumers may exchange ptr with a nullptr.

  • If there was no data, there still will not be any data and the consumer will have to try again later (this effectively builds a spinlock).

  • If there was data, the consumer now takes ownership and becomes responsible for deleting it when it is no longer needed.

To produce data, a producer exchanges ptr with a pointer to the produced data.

  • If there was no data, the previous pointer will be equal to nullptr and data was successfully produced.
  • If there was data, the producer effectively takes back ownership of the previously produced data. It can then either delete the object, or - more effectively - simply reuse it for its next production.
danielschemmel
  • 10,885
  • 1
  • 36
  • 58
  • OK, that will work with 1 consumer and 1 producer. In my case I have 1 producer and a lot of consumers. – atlaste Mar 25 '15 at 12:10
  • @atlaste It will still work. Any consumer can exchange a `nullptr` in, and then either act on the received value or wait until it can get one (effectively creating a kind of spinlock). – danielschemmel Mar 25 '15 at 12:11
  • Ah I see what you mean. My situation is a bit more complicated. Consumers always have data in my case and work on the data. Producers (1) run a long-running process that update the data periodically, always from the same thread. Data should be deleted when it is no longer used, e.g. after the periodic update and when it is not used anymore in a consumer. Think of it like a linked list where producers merge links a-synchonously, and consumers merely work on the linked list. – atlaste Mar 25 '15 at 12:22
  • @atlaste Your use-case sounds complicated enough that you might want to simply throw a reader-writer-lock around a `shared_ptr`. You will need a similar construct anyway, because you cannot make any real guarantees as to the intended lifetime of the objects ("when it is no longer used"). – danielschemmel Mar 25 '15 at 12:30
  • @gha-st Yes, and if it wasn't one of the things that's going to be a serious performance bottleneck, I would probably do just that... what I really need is indeed something like a shared_atomic_ptr . – atlaste Mar 25 '15 at 12:48
  • @atlaste Assuming there are only a few consumers, you can create a vector of pointers, one for each target. You can then implement reference counting such that the reference count is initialized by the producer with the number of consumers. – danielschemmel Mar 25 '15 at 14:28
  • @gha-st Yes that was also one idea, but it's not going to work. I'm just going to have to get back to the drawing board, that happens... ctrl-a,del - done, time to try again. :-) Your answers have been a help though and it does answer my original question - so thanks, +1, and -check-. :-) – atlaste Mar 25 '15 at 19:12