3

I started learning multi-threading and came across futures and promises for synchronizing threads over shared resources. So, I thought of implementing a famous Double Buffering problem using Futures and Promises( single producer and single consumer). The basic methodology what I have thought is :

ProducerThread:

loop:    
    locks_buffer1_mutex    
    fills_buffer1   
    unlocks_buffer1_mutex    
    passes number 1 to Consumer thread using promise.setvalue()    
    locks_buffer2_mutex    
    fills_buffer2   
    unlocks_buffer2_mutex    
    passes number 2 to Consumer thread using promise.setvalue()
back_to_loop

ConsumerThread :

loop:
   wait_for_value_from_promise
   switch
      case 1:
         lock_buffer1_mutex
          process(buffer1)
         unlock_buffer1_mutex
         print_values
         break
       case 2:
         lock_buffer2_mutex
         process(buffer2)
         unlock_buffer2_mutex
         print_values
         break
back_to_loop

Here is the code:

#include <iostream>
#include <thread>
#include <vector>
#include <future>
#include <mutex>
#include <iterator>


std::mutex print_mutex;
std::mutex buffer1_mutex;
std::mutex buffer2_mutex;

std::vector<int> buffer1;
std::vector<int> buffer2;

bool notify;


void DataAcquisition(std::promise<int> &p)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    while(true)
    {
        {
            std::lock_guard<std::mutex> buff1_lock(buffer1_mutex);
            for(int i=0;i<200;i++)
            {
                buffer1.push_back(i);
            }
        }
        p.set_value(1);
        {
            std::lock_guard<std::mutex> buff2_lock(buffer2_mutex);
            for(int i=0;i<200;i++)
            {
                buffer2.push_back(199-i);
            }
        }
        p.set_value(2);
    }
}

void DataExtraction(std::future<int> &f)
{
    std::vector<int>::const_iterator first,last;
    std::vector<int> new_vector;
    std::ostream_iterator<int> outit(std::cout, " ");

    while(true)
    {
        int i = f.get();
        std::cout << "The value of i is :" << i << std::endl;
        switch(i)
        {
            case 1:
                {
                    std::lock_guard<std::mutex> buff1_lock(buffer1_mutex);
                    first = buffer1.begin();
                    last = first + 10;
                }
                new_vector = std::vector<int>(first,last);
                {
                    std::lock_guard<std::mutex> print_lock(print_mutex);
                    std::copy(new_vector.begin(),new_vector.end(),outit);
                }
                break;
              case 2:
                {
                    std::lock_guard<std::mutex> buff2_lock(buffer2_mutex);
                    first = buffer2.begin();
                    last = first + 10;
                }
                new_vector = std::vector<int>(first,last);
                {
                    std::lock_guard<std::mutex> print_lock(print_mutex);
                    std::copy(new_vector.begin(),new_vector.end(),outit);
                }
                break;
           }
    }
}

int main()
{
    std::promise<int> p;
    std::future<int> f = p.get_future();


    std::thread thread1(DataAcquisition,std::ref(p));
    std::thread thread2(DataExtraction,std::ref(f));

    thread1.join();
    thread2.join();

    return 0;
}

When I execute this code I came across through his giagntic problem, which I am fully unaware of

terminate called after throwing an instance of 'std::future_error' terminate called recursively
  what(): 0 1 2 3 4 5 6 7 8 9 Promise already satisfied
Press <RETURN> to close the window

I have googled about this error, it is suggested to link -lpthread switch during linking and compile time. but could n't resolve the issue.

Please help me, Where am i going wrong..

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
jeldikk
  • 353
  • 2
  • 12
  • For what you're trying to do wouldn't an atomic be sufficient to indicate the most recently completed buffer? Notice that there is no guarantee that the 'Consumer' will see all the frames produced by the 'Producer'. That's easy to see intuitively if you imagine 'print values' is slow. The Consumer holds no lock during 'print values' and the 'Producer' could rattle round producing any number of frames and over writing them. Even if you don't mind about 'dropping frames' a more subtle co-operation would mean you don't waste CPU cycles producing output no one ever sees. – Persixty Apr 28 '15 at 09:58
  • By a similar argument `Consumer` might process the same frame twice. – Persixty Apr 28 '15 at 10:03

1 Answers1

3

You can't call set_value for a promise more that once, which is illustrated by the following code:

#include <future>

int main() {
    std::promise<int> p;
    p.set_value(1);
    p.set_value(2); // Promise already satisfied
}

You have to look for another approach. For example, you could use two std::condition_variables - set them in producer, and wait for them in consumer.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Hi Anton, You mean we can't call set_value on a promise more than once in the whole execution of the problem...?? The suggestion you gave regarding reading buffers "in turn", I could n't get exactly what you are suggesting me to do. – jeldikk Apr 28 '15 at 10:00
  • 1
    @user1511151 yes, once the value is set, you can't do it anymore. Regarding "in turn" it seems it won't work easily, you can use condition variables, see my update. – Anton Savin Apr 28 '15 at 10:07
  • Right, a promise is just a value + time, once the value is there it's impossible to set it again - this is like trying to set 5 twice. – Benjamin Gruenbaum Apr 28 '15 at 12:30
  • @Anton, You mean to say promise is only used to make state change but return a type value. We have condition_variable to have state change. Then what does promise's unique nature..?? – jeldikk Apr 29 '15 at 03:43