Reputation: 24151
I am writing to, and reading from, a file called "results.dat", using std::fstream
. This is done asynchronously, with the "read" and "write" function running in entirely different C++ programs. I want to check that the file is not currently being read before I write to it.
Suppose that my data is an array of 10 floats, then my "write" function looks like:
std::ofstream file_out("results.dat", std::ios::binary | std::ios::trunc);
file_out.write((char*)&data, 10 * sizeof(float));
file_out.close();
And my "read" function looks like:
std::ifstream file_in("results.dat", std::ios::binary);
file_in.read((char*)&data, 10 * sizeof(float));
file_in.close();
If the file "results.dat" is currently being read, I then want to make my "write" function wait, in a loop, until it is no longer being read. How can I implement this?
Upvotes: 1
Views: 2450
Reputation: 69912
If both processes are running on the same machine, then you can use an interprocess mutex. Only one process at a time is allowed to interact with the data file (the resource).
Interprocess communication is different on all OSs, but the boost library provides a uniform interface for all common systems.
The code would look something like this:
writer:
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
using namespace boost::interprocess;
named_mutex mutex(open_or_create, "data_file_access_mutex");
void write_function() {
scoped_lock<named_mutex> lock(mutex);
std::ofstream file_out("results.dat", std::ios::binary | std::ios::trunc);
file_out.write((char*)&data, 10 * sizeof(float));
file_out.close();
}
reader:
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
using namespace boost::interprocess;
named_mutex mutex(open_or_create, "data_file_access_mutex");
void read_function() {
scoped_lock<named_mutex> lock(mutex);
std::ifstream file_in("results.dat", std::ios::binary);
file_in.read((char*)&data, 10 * sizeof(float));
file_in.close();
}
Upvotes: 0
Reputation: 6467
For synchronous cases std::ios::tie
.
The tie()
is used to ensure that output from a tied stream appears before an input from the stream to
which it is tied. For example, cout
is tied to cin
:
cout << "Please enter a number: ";
int num;
cin >> num;
This code does not explicitly call cout.flush()
, so had cout
not been tied to cin
, the user would see the request for input.
Here is another example:
#include <iostream> // std::ostream, std::cout, std::cin
#include <fstream> // std::ofstream
int main () {
std::ostream *prevstr;
std::ofstream ofs;
ofs.open ("test.txt");
std::cout << "tie example:\n";
*std::cin.tie() << "This is inserted into cout";
prevstr = std::cin.tie (&ofs);
*std::cin.tie() << "This is inserted into the file";
std::cin.tie (prevstr);
ofs.close();
return 0;
}
Further reading here.
For asynchronous cases using flock()
:
Example:
The lock file creation must be atomic. The pre-standard <fstream.h>
library used to have an ios::noshare
flag that guaranteed atomic file creation. Sadly, it was removed from the library, which superseded <fstream.h>
. As a result, we are forced to use the traditional Unix file I/O interface declared in <fcntl.h>
(under Unix and Linux) or <io.h>
(Windows) to ensure an atomic operation.
Before a process can write to the data file, it should obtain a lock like this:
#include <fcntl.h> // for open()
#include <cerrno> // for errno
#include <cstdio> // for perror()
int fd;
fd=open("password.lck", O_WRONLY | O_CREAT | O_EXCL)
If the open()
call succeeds, it returns a descriptor, which is a small positive integer that identifies the file. Otherwise, it returns -1
and assigns a matching error code to the global variable errno
. The O_CREAT
flag indicates that if the file doesn't exist, open()
should create it. The O_EXCL
flag ensures that the call is atomic
; if the file already exists, open()
will fail and set errno
to EEXIST
. This way you guarantee that only a single process at a time can hold the lock.
You check the return code of open()
as follows:
int getlock() // returns the lock's descriptor on success
{
if (fd<0 && errno==EEXIST){
// the file already exist; another process is
// holding the lock
cout<<"the file is currently locked; try again later";
return -1;
}
else if (fd < 0){
// perror() appends a verbal description of the current
// errno value after the user-supplied string
perror("locking failed for the following reason");
return -1;
}
// if we got here, we own the lock
return fd;
}
Once a process owns the lock, it can write to the data file safely. When it has finished updating the file, it should delete the lock as follows:
remove("password.lck");
At this moment, the data file is considered unlocked and another process may access it.
or using: pthread_rwlockattr
#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include "check.h"
pthread_rwlock_t rwlock;
void *rdlockThread(void *arg)
{
int rc;
printf("Entered thread, getting read lock\n");
rc = pthread_rwlock_rdlock(&rwlock);
checkResults("pthread_rwlock_rdlock()\n", rc);
printf("got the rwlock read lock\n");
sleep(5);
printf("unlock the read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
checkResults("pthread_rwlock_unlock()\n", rc);
printf("Secondary thread unlocked\n");
return NULL;
}
void *wrlockThread(void *arg)
{
int rc;
printf("Entered thread, getting write lock\n");
rc = pthread_rwlock_wrlock(&rwlock);
checkResults("pthread_rwlock_wrlock()\n", rc);
printf("Got the rwlock write lock, now unlock\n");
rc = pthread_rwlock_unlock(&rwlock);
checkResults("pthread_rwlock_unlock()\n", rc);
printf("Secondary thread unlocked\n");
return NULL;
}
int main(int argc, char **argv)
{
int rc=0;
pthread_t thread, thread1;
printf("Enter Testcase - %s\n", argv[0]);
printf("Main, initialize the read write lock\n");
rc = pthread_rwlock_init(&rwlock, NULL);
checkResults("pthread_rwlock_init()\n", rc);
printf("Main, grab a read lock\n");
rc = pthread_rwlock_rdlock(&rwlock);
checkResults("pthread_rwlock_rdlock()\n",rc);
printf("Main, grab the same read lock again\n");
rc = pthread_rwlock_rdlock(&rwlock);
checkResults("pthread_rwlock_rdlock() second\n", rc);
printf("Main, create the read lock thread\n");
rc = pthread_create(&thread, NULL, rdlockThread, NULL);
checkResults("pthread_create\n", rc);
printf("Main - unlock the first read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
checkResults("pthread_rwlock_unlock()\n", rc);
printf("Main, create the write lock thread\n");
rc = pthread_create(&thread1, NULL, wrlockThread, NULL);
checkResults("pthread_create\n", rc);
sleep(5);
printf("Main - unlock the second read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
checkResults("pthread_rwlock_unlock()\n", rc);
printf("Main, wait for the threads\n");
rc = pthread_join(thread, NULL);
checkResults("pthread_join\n", rc);
rc = pthread_join(thread1, NULL);
checkResults("pthread_join\n", rc);
rc = pthread_rwlock_destroy(&rwlock);
checkResults("pthread_rwlock_destroy()\n", rc);
printf("Main completed\n");
return 0;
}
Here are few useful resources: 1, 2 and 3.
Upvotes: 1