Karnivaurus
Karnivaurus

Reputation: 24151

Checking whether a file is being written to with std::fstream

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

Answers (4)

Richard Hodges
Richard Hodges

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

Ziezi
Ziezi

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

adjan
adjan

Reputation: 13684

Use the LockFile() function on Windows or flock() for Linux/Unix.

Upvotes: 2

Andrew Henle
Andrew Henle

Reputation: 1

Linux/Unix? Use a process-shared pthread_rwlock.

Upvotes: 0

Related Questions