I'm working with an API that retrieves I/Q data. Calling the function bbGetIQ(m_handle, &pkt);fills a buffer. This is a thread looping while the user hasn't input "stop". Pkt is a structure and the buffer used is pkt.iqData = &m_buffer[0]; which is a vector of float. The size of the vector is 5000 and each time we're looping the buffer is filled with 5000 values.
I want to save the data from the buffer into a file, and I was doing it right after a call to bbgetIQ but doing like so is a time consuming task, data wasn't retrieved fast enough resulting in the API dropping data so it can continue filling its buffer.
Here's what my code looked like :
void Acquisition::recordIQ(){
int cpt = 0;
ofstream myfile;
while(1){
while (keep_running)
{
cpt++;
if(cpt < 2)
myfile.open ("/media/ssd/IQ_Data.txt");
bbGetIQ(m_handle, &pkt); //Retrieve I/Q data
//Writing content of buffer into the file.
for(int i=0; i<m_buffer.size(); i++)
myfile << m_buffer[i] << endl;
}
cpt = 0;
myfile.close();
}
}
Then i tried to only write into the file when we leave the loop :
void Acquisition::recordIQ(){
int cpt = 0;
ofstream myfile;
int next=0;
vector<float> data;
while(1){
while ( keep_running)
{
if(keep_running == false){
myfile.open ("/media/ssd/IQ_Data.txt");
for(int i=0; i<data.size(); i++)
myfile << data[i] << endl;
myfile.close();
break;
}
cpt++;
data.resize(next + m_buffer.size());
bbGetIQ(m_handle, &pkt); //retrieve data
std::copy(m_buffer.begin(), m_buffer.end(), data.begin() + next); //copy content of the buffer into final vector
next += m_buffer.size(); //next index
}
cpt = 0;
}
}
I am no longer getting data loss from the API, but the issue is that i'm limited by the size of data vector. For example, I can't let it retrieve data all night.
My idea is to make 2 threads. One will retrieve data and the other will write the data into a file. The 2 threads will share a circular buffer where the first thread will fill the buffer and the second thread will read the buffer and write the content to a file. As it is a shared buffer, i guess i should use mutexes.
I'm new to multi-threading and mutex, so would this be a good idea? I don't really know where to start and how the consumer thread can read the buffer while the producer will fill it. Will locking the buffer while reading cause data drop by the API ? (because it won't be able to write it into the circular buffer).
EDIT : As i want my record thread to run in background so i can do other stuff while it's recording, i detached it and the user can launch a record by setting the condition keep_running to true.
thread t1(&Acquisition::recordIQ, &acq);
t1.detach();
You need to use something like this (https://en.cppreference.com/w/cpp/thread/condition_variable):
globals:
std::mutex m;
std::condition_variable cv;
std::vector<std::vector<float>> datas;
bool keep_running = true, start_running = false;
writing thread:
void writing_thread()
{
myfile.open ("/media/ssd/IQ_Data.txt");
while(1) {
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return keep_running && !datas.empty();});
if (!keep_running) break;
auto d = std::move(datas);
lk.unlock();
for(auto &entry : d) {
for(auto &e : entry)
myfile << e << endl;
}
}
}
sending thread:
void sending_thread() {
while(1) {
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return keep_running && start_running;});
if (!keep_running) break;
}
bbGetIQ(m_handle, &pkt); //retrieve data
std::vector<float> d = m_buffer;
{
std::lock_guard<std::mutex> lk(m);
if (!keep_running) break;
datas.push_back(std::move(d));
}
cv.notify_one();
}
}
void start() {
{
std::unique_lock<std::mutex> lk(m);
start_running = true;
}
cv.notify_all();
}
void stop() {
{
std::unique_lock<std::mutex> lk(m);
start_running = false;
}
cv.notify_all();
}
void terminate() {
{
std::unique_lock<std::mutex> lk(m);
keep_running = false;
}
cv.notify_all();
thread1.join();
thread2.join();
}
In short:
Sending thread receives data from whatever it comes, locks mutex mt and moves data to datas storage. Then it uses cv condition variable to notify waiting threads, that there's something to do. Writing thread waits for condition variable to be signaled, then locks mutex mt, moves data from datas global variable to local, then releases mutex and proceed to write just received data to file. Key is to keep mutexed locked for least time possible.
EDIT:
to terminate whole thing you need to set keep_running to false. Then call cv.notify_all(). Then join threads involved. Order is important. You need to join threads, because writing thread might be still in process of writing data.
EDIT2:
added delayed start. Now create two threads, in one run sending_thread, in other writing_thread. Call start() to enable processing and stop() to stop it.
Related
In trying to create an asynchronous I/O file reader in C++ under Linux. The example I have has two buffers. The first read blocks. Then, for each time around the main loop, I asynchronously launch the IO and call process() which runs the simulated processing of the current block. When processing is done, we wait for the condition variable. The idea is that the asynchronous handler should notify the condition variable.
Unfortunately the notify seems to happen before wait, and it seems like this is not the way the condition variable wait() function works. How should I rewrite the code so that the loop waits until the asynchronous io has completed?
#include <aio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <condition_variable>
#include <cstring>
#include <iostream>
#include <thread>
using namespace std;
using namespace std::chrono_literals;
constexpr uint32_t blockSize = 512;
mutex readMutex;
condition_variable cv;
int fh;
int bytesRead;
void process(char* buf, uint32_t bytesRead) {
cout << "processing..." << endl;
usleep(100000);
}
void aio_completion_handler(sigval_t sigval) {
struct aiocb* req = (struct aiocb*)sigval.sival_ptr;
// check whether asynch operation is complete
if (aio_error(req) == 0) {
int ret = aio_return(req);
bytesRead = req->aio_nbytes;
cout << "ret == " << ret << endl;
cout << (char*)req->aio_buf << endl;
}
{
unique_lock<mutex> readLock(readMutex);
cv.notify_one();
}
}
void thready() {
char* buf1 = new char[blockSize];
char* buf2 = new char[blockSize];
aiocb cb;
char* processbuf = buf1;
char* readbuf = buf2;
fh = open("smallfile.dat", O_RDONLY);
if (fh < 0) {
throw std::runtime_error("cannot open file!");
}
memset(&cb, 0, sizeof(aiocb));
cb.aio_fildes = fh;
cb.aio_nbytes = blockSize;
cb.aio_offset = 0;
// Fill in callback information
/*
Using SIGEV_THREAD to request a thread callback function as a notification
method
*/
cb.aio_sigevent.sigev_notify_attributes = nullptr;
cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
cb.aio_sigevent.sigev_notify_function = aio_completion_handler;
/*
The context to be transmitted is loaded into the handler (in this case, a
reference to the aiocb request itself). In this handler, we simply refer to
the arrived sigval pointer and use the AIO function to verify that the request
has been completed.
*/
cb.aio_sigevent.sigev_value.sival_ptr = &cb;
int currentBytesRead = read(fh, buf1, blockSize); // read the 1st block
while (true) {
cb.aio_buf = readbuf;
aio_read(&cb); // each next block is read asynchronously
process(processbuf, currentBytesRead); // process while waiting
{
unique_lock<mutex> readLock(readMutex);
cv.wait(readLock);
}
currentBytesRead = bytesRead; // make local copy of global modified by the asynch code
if (currentBytesRead < blockSize) {
break; // last time, get out
}
cout << "back from wait" << endl;
swap(processbuf, readbuf); // switch to other buffer for next time
currentBytesRead = bytesRead; // create local copy
}
delete[] buf1;
delete[] buf2;
}
int main() {
try {
thready();
} catch (std::exception& e) {
cerr << e.what() << '\n';
}
return 0;
}
A condition varible should generally be used for
waiting until it is possible that the predicate (for example a shared variable) has changed, and
notifying waiting threads that the predicate may have changed, so that waiting threads should check the predicate again.
However, you seem to be attempting to use the state of the condition variable itself as the predicate. This is not how condition variables are supposed to be used and may lead to race conditions such as those described in your question. Another reason to always check the predicate is that spurious wakeups are possible with condition variables.
In your case, it would probably be appropriate to create a shared variable
bool operation_completed = false;
and use that variable as the predicate for the condition variable. Access to that variable should always be controlled by the mutex.
You can then change the lines
{
unique_lock<mutex> readLock(readMutex);
cv.notify_one();
}
to
{
unique_lock<mutex> readLock(readMutex);
operation_completed = true;
cv.notify_one();
}
and change the lines
{
unique_lock<mutex> readLock(readMutex);
cv.wait(readLock);
}
to:
{
unique_lock<mutex> readLock(readMutex);
while ( !operation_completed )
cv.wait(readLock);
}
Instead of
while ( !operation_completed )
cv.wait(readLock);
you can also write
cv.wait( readLock, []{ return operation_completed; } );
which is equivalent. See the documentation of std::condition_varible::wait for further information.
Of course, operation_completed should also be set back to false when appropriate, while the mutex is locked.
The following simplified example of several
I'm writing a c++20 software which explits pthreads. The simplified example shows how I have a shared resource shared_resource, an int variable, which is written by several threads, several times. To access the variable I use a mutex and a condition variable. A typical use of mutex and condition variables.
the num_readers is used as following:
greater than 0: multiple readers accessing the shared variable
0: neither writers nor readers are accessing the resource
-1: a writer is writing a new value on the resource. No more readers nor writers are avaibale until the writer releases the resource
The simplified version has no readers for focusing on the problem. Since num_readers = num_readers - 1; can be executed only when a writer releases the resource by setting it to 0 and signaling the other writers, I expect 0 or -1 values, but never -2!
The problem is that by executing the following I randomly get -2 values, so some interleaving problem is occurring I guess:
WAT>? num_readers -2
Process finished with exit code 1
#include <iostream>
#include <pthread.h>
#include <cstdlib>
#include <thread>
#include <random>
void* writer(void* parameters);
pthread_mutex_t mutex{PTHREAD_MUTEX_DEFAULT};
pthread_cond_t cond_writer = PTHREAD_COND_INITIALIZER;
int num_readers{0};
int shared_resource{0};
int main() {
const int WRITERS{500};
pthread_t writers[WRITERS];
for(unsigned int i=0; i < WRITERS; i++) {
pthread_create(&writers[i], NULL, writer, NULL);
}
for(auto &writer_thread : writers) {
pthread_join(writer_thread, NULL);
std::cout << "[main] writer returned\n";
}
std::cout << "[main] exiting..." << std::endl;
return 0;
}
void* writer(void* parameters) {
for (int i=0; i<5; i++) {
pthread_mutex_lock(&mutex);
while(num_readers != 0) {
if (num_readers < -1) {
std::cout << "WAT>? num_readers " << std::to_string(num_readers) << "\n";
exit(1);
}
pthread_cond_wait(&cond_writer, &mutex);
}
num_readers = num_readers - 1;
pthread_mutex_unlock(&mutex);
std::uniform_int_distribution<int> dist(1, 1000);
std::random_device rd;
int new_value = dist(rd);
shared_resource = new_value;
pthread_mutex_lock(&mutex);
num_readers = 0;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond_writer);
}
return 0;
}
So: why isn't this code thread safe?
Some issues stand out in your code:
You modify the number of readers in the write funtion. Only the reader function should do that.
Same thing for the signaling of the condition variable. That should only be signaled from the reader function.
incrementing and decrementing the number of readers is usually done with a semaphore: an atomic int and an associated condition variable.
Here is the algorithm:
int reader()
{
// indicate that a read is in progress.
//
// a. lock()/
// b. increment number of readers.
// c. unlock() as soon as possible, so other readers can also read reading.
//
// note that any write in progress will stop the thread here.
pthread_mutex_lock(&mutex);
++num_readers;
pthread_mutex_unlock(&mutex);
// read protected data
int result = shared_resource;
// decremennt readers count.
//
// note that calls to lock()/unlock() are not necessary if
// num_readers is atomic (I.e.: std::atomic<int>)
pthread_mutex_lock(&mutex);
if (--num_readers == 0)
pthread_cond_signal(&cond_writer); // last reader sets the cond_var
pthread_mutex_unlock(&mutex);
return result;
}
void writer(int value)
{
// lock
pthread_mutex_lock(&mutex);
// wait for no readers, the mutex is released while waiting for
// the last read to complete. Note that access to num_readers is
// done while the mutex is owned.
while (num_readers != 0)
pthread_cond_wait(&cond_writer, &mutex);
// modify protected data.
shared_resource = value;
// unlock.
pthread_mutex_unlock(&mutex);
}
I want to synchronize the output of two sensors that works at different frame rate (~80ms vs ~40ms) in C++ using threads. The idea is like the producer-consumer problem but with 2 producers and 1 consumer, and without a buffer because only the last new products matters.
These are the points that shoud cover the problem:
Each sensor reading will be managed by a thread separately.
There will be a main thread that must take always the last new two data read from the sensors and process it.
The reading of one sensor should not block the reading of the other. I mean, the threads reading should not have the same mutex.
The main/process thread should not block the reading threads while it is working. I propose lock the data, make a local copy (it is faster than process directly), unlock and process the copy.
If there is no new data, the main thread should wait for it.
This is a time diagram of the requested functionality.
And this is the pseudocode:
void getSensor1(Data& data)
{
while (true)
{
mutex1.lock();
//Read data from sensor 1
mutex1.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(80 + (rand() % 5)));
}
}
void getSensor2(Data& data)
{
while (true)
{
mutex2.lock();
//Read data from sensor 2
mutex2.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(40 + (rand() % 5)));
}
}
int main()
{
Data sensor1;
Data sensor2;
std::thread threadGetScan(getSensor1, std::ref(sensor1));
std::thread threadGetFrame(getSensor2, std::ref(sensor2));
while(true)
{
// Wait for new data, lock, copy, unlock and process it
std::this_thread::sleep_for(std::chrono::milliseconds(100 + (rand() % 25)))
}
return 0;
}
Thanks in advance.
Since each sensor is only read from one thread, then mutex around the sensor access serves no purpose. You can get rid of that. Where you need thread safety is the means by which the thread which has read from a sensor passes data to the thread which is consuming it.
Have the thread reading from the sensor use only local variables, or variables only accessed by that thread, for its work of reading the sensor. Once it has the data completely, then put that data (or better yet, a pointer to the data) into a shared queue that the consuming thread will get it from.
Since you need to save only the latest data, your queue can have a max size of 1. Which can just be a pointer.
Access to this shared data structure should be protected with a mutex. But since it is just a single pointer, you can use std::atomic.
The reading thread could look like this:
void getData(std::atomic<Data*>& dataptr) {
while (true) {
Data* mydata = new Data; // local variable!
// stuff to put data into mydata
std::this_thread::sleep_for(80ms);
// Important! this line is only once that uses dataptr. It is atomic.
Data* olddata = std::atomic_exchange(&dataptr, mydata);
// In case the old data was never consumed, don't leak it.
if (olddata) delete olddata;
}
}
And the main thread could look like this:
void main_thread(void) {
std::atomic<Data*> sensorData1;
std::atomic<Data*> sensorData2;
std::thread sensorThread1(getData, std::ref(sensorData1));
std::thread sensorThread2(getData, std::ref(sensorData2));
while (true) {
std::this_thread::sleep_for(100ms);
Data* data1 = std::atomic_exchange(&sensorData1, (Data*)nullptr);
Data* data2 = std::atomic_exchange(&sensorData2, (Data*)nullptr);
// Use data1 and data2
delete data1;
delete data2;
}
}
After some researching work, I have found a solution that does what I wanted using mutexes and condition variables. I let you below the code I propose. Improvements and other suitable solutions are still accepted.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <cstdlib>
#define SIZE_LOOP 1000
// Struct where the data sensors is synchronized
struct Data
{
int data1; // Data of sensor 1
int data2; // Data of sensor 2
};
std::mutex mtx1; // Mutex to access sensor1 shared data
std::mutex mtx2; // Mutex to access sensor2 shared data
std::condition_variable cv1; // Condition variable to wait for sensor1 data availability
std::condition_variable cv2; // Condition variable to wait for sensor2 data availability
bool ready1; // Flag to indicate sensor1 data is available
bool ready2; // Flag to indicate sensor2 is available
// Function that continuously reads data from sensor 1
void getSensor1(int& data1)
{
// Initialize flag to data not ready
ready1 = false;
// Initial delay
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
// Reading loop (i represents an incoming new data)
for(int i = 0; i < SIZE_LOOP; i++)
{
// Lock data access
std::unique_lock<std::mutex> lck1(mtx1);
// Read data
data1 = i;
std::cout << "Sensor1 (" << data1 << ")"<< std::endl;
// Set data to ready
ready1 = true;
// Notify if processing thread is waiting
cv1.notify_one();
// Unlock data access
lck1.unlock();
// Sleep to simulate frame rate
std::this_thread::sleep_for(std::chrono::milliseconds(2000 + (rand() % 500)));
}
}
// Function that continuously reads data from sensor 2
void getSensor2(int& data2)
{
// Initialize flag to data not ready
ready2 = false;
// Initial delay
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
// Reading loop (i represents an incoming new data)
for(int i = 0; i < SIZE_LOOP; i++)
{
// Lock data access
std::unique_lock<std::mutex> lck2(mtx2);
// Read data
data2 = i;
std::cout << "Sensor2 (" << data2 << ")"<< std::endl;
// Set data to ready
ready2 = true;
// Notify if processing thread is waiting
cv2.notify_one();
// Unlock data access
lck2.unlock();
// Sleep to simulate frame rate
std::this_thread::sleep_for(std::chrono::milliseconds(1000 + (rand() % 500)));
}
}
// Function that waits until sensor 1 data is ready
void waitSensor1(const int& dataRead1, int& dataProc1)
{
// Lock data access
std::unique_lock<std::mutex> lck1(mtx1);
// Wait for new data
while(!ready1)
{
//std::cout << "Waiting sensor1" << std::endl;
cv1.wait(lck1);
}
//std::cout << "No Waiting sensor1" << std::endl;
// Make a local copy of the data (allows uncoupling read and processing tasks what means them can be done parallely)
dataProc1 = dataRead1;
std::cout << "Copying sensor1 (" << dataProc1 << ")"<< std::endl;
// Sleep to simulate copying load
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// Set data flag to not ready
ready1 = false;
// Unlock data access
lck1.unlock();
}
// Function that waits until sensor 2 data is ready
void waitSensor2(const int& dataRead2, int& dataProc2)
{
// Lock data access
std::unique_lock<std::mutex> lck2(mtx2);
// Wait for new data
while(!ready2)
{
//std::cout << "Waiting sensor2" << std::endl;
cv2.wait(lck2);
}
//std::cout << "No Waiting sensor2" << std::endl;
// Make a local copy of the data (allows uncoupling read and processing tasks what means them can be done parallely)
dataProc2 = dataRead2;
std::cout << "Copying sensor2 (" << dataProc2 << ")"<< std::endl;
// Sleep to simulate copying load
std::this_thread::sleep_for(std::chrono::milliseconds(400));
// Set data flag to not ready
ready2 = false;
// Unlock data access
lck2.unlock();
}
// Main function
int main()
{
Data dataRead; // Data read
Data dataProc; // Data to process
// Threads that reads at some frame rate data from sensor 1 and 2
std::thread threadGetSensor1(getSensor1, std::ref(dataRead.data1));
std::thread threadGetSensor2(getSensor2, std::ref(dataRead.data2));
// Processing loop
for(int i = 0; i < SIZE_LOOP; i++)
{
// Wait until data from sensor 1 and 2 is ready
std::thread threadWaitSensor1(waitSensor1, std::ref(dataRead.data1), std::ref(dataProc.data1));
std::thread threadWaitSensor2(waitSensor2, std::ref(dataRead.data2), std::ref(dataProc.data2));
// Shyncronize data/threads
threadWaitSensor1.join();
threadWaitSensor2.join();
// Process synchronized data while sensors are throwing new data
std::cout << "Init processing (" << dataProc.data1 << "," << dataProc.data2 << ")"<< std::endl;
// Sleep to simulate processing load
std::this_thread::sleep_for(std::chrono::milliseconds(10000 + (rand() % 1000)));
std::cout << "End processing" << std::endl;
}
return 0;
}
I'm looking at this Boost example code for two processes sharing a mutex and condition variable between them:
https://www.boost.org/doc/libs/1_57_0/doc/html/interprocess/synchronization_mechanisms.html
but I don't understand how the mutex-condition variable design here can work.
The initial process calls:
for(int i = 0; i < NumMsg; ++i){
scoped_lock<interprocess_mutex> lock(data->mutex); // Take mutex
if(data->message_in){
data->cond_full.wait(lock); // Wait
}
if(i == (NumMsg-1))
std::sprintf(data->items, "%s", "last message");
else
std::sprintf(data->items, "%s_%d", "my_trace", i);
//Notify to the other process that there is a message
data->cond_empty.notify_one(); // Notify
//Mark message buffer as full
data->message_in = true;
}
and the second process calls:
bool end_loop = false;
do{
scoped_lock<interprocess_mutex> lock(data->mutex); // Take mutex
if(!data->message_in){
data->cond_empty.wait(lock); // Wait
}
if(std::strcmp(data->items, "last message") == 0){
end_loop = true;
}
else{
//Print the message
std::cout << data->items << std::endl;
//Notify the other process that the buffer is empty
data->message_in = false;
data->cond_full.notify_one(); // Notify
}
}
while(!end_loop);
To call wait() or notify() either process must hold the shared mutex, so if one process is on wait() the other surely cannot call notify()?
wait releases the mutex while waiting, so the other thread can acquire the mutex and perform the notify.
Also see the description on https://www.boost.org/doc/libs/1_57_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions.conditions_whats_a_condition.
I read data from a file and process it in a seperate thread. I am trying to parallelize the data read and processing parts on two threads and using conditional variables with infinite loops.
However, I end up with deadlocks.
char totBuf[300000];
unsigned long toLen = 0;
unsigned long lenProc = 0;
void procData()
{
while(lenProc < totLen)
{
//process data from totBuf
//increment lenProc;
}
ready = false;
if(lenProc >= totLen && totLen > 100000)
{
cv.notify_one();
unique_lock<mutex> lk(m);
cv.wait(lk, []{return totLen>0 && lenProc<totLen;});
}
}
void readData()
{
//declared so that we notify procData only once
bool firstNot = true;
while(true)
{
//read data into
//file.read(len);
//file.read(oBf, len);
memcpy(&totBuf[totLen], oBf, len);
//increment totLen
if(totLen > 10000)
cv.notify_one();
if(totLen > 100000)
{
cv.notify_one();
unique_lock<mutex> lk(m);
cv.wait_for(lk, []{return !ready;});
totLen = 0;
firstNot = true;
lenProc = 0;
}
}
}
int main(int argc, char* argv[])
{
inFile.open(argv[1], ios::in|ios::binary);
thread prod(readData);
thread cons(procSeqMsg);
prod.join();
ready = true;
cout << "prod joined\n";
cv.notify_all();
cons.join();
cout << "cons joined\n";
inFile.close();
return(0);
}
Some explanations if it looks weird. Though I have declared totBuf size to 300k but i reset totLen to 0 when its 100k because i read data in chunks from the file where the new chunks could be big. When size reaches 100k I reset totLen to read data at begining of totBuf again.
I notify the consumer first when size reaches 10k so that maximum concurrent processing can be achieved.
This could be a really bad design afaik and am willing to redesign from scratch. What I want is total lockfree implementation but am new to threads hence this is done as a stop-gap/ best I could do right now.