5

In C++ is there any way to make the writing into file thread safe in the following scenario ?

void foo_one(){
lock(mutex1);
//open file abc.txt
//write into file
//close file
unlock(mutex1);
}

void foo_two(){
lock(mutex2);
//open file abc.txt
//write into file
//close file
unlock(mutex2);
}

In my application (multi-threaded) , it is likely that foo_one() and foo_two() are executed by two different threads at the same time . Is there any way to make the above thread safe ?

I have considered using the file-lock ( fcntl and/or lockf ) but not sure how to use them because fopen() has been used in the application ( performance reasons ) , and it was stated somewhere that those file locks should not be used with fopen ( because it is buffered )

PS : The functions foo_one() and foo_two() are in two different classes , and there is no way to have a shared data between them :( , and sadly the design is such that one function cannot call other function .

k0n3ru
  • 683
  • 1
  • 8
  • 25
  • Related: http://stackoverflow.com/questions/7565034/can-multiple-threads-write-into-a-file-simultaneously-if-all-the-threads-are-wr – jogojapan Oct 15 '12 at 05:18
  • Yes. You just need a lock that represents the file (call it mutex3). PS. If you are writing C++ code you should be using RAII to lock/unlock those mutexes. – Martin York Oct 15 '12 at 05:21
  • @LokiAstari The functions foo_one() and foo_two() are in two different classes , and there is no way to have a shared resource between them :( , and sadly the design is such that one function cannot call other function . – k0n3ru Oct 15 '12 at 05:29
  • Could just make a "Write" function of a class, that gets called on it's own thread. Just looping until it's quit. Then every time you need to write something to file, you can just push it into the vector and have that thread write it to file as soon as it arrives. – M4rc Oct 15 '12 at 05:30
  • Then add a function that does the write and syncronization. – Martin York Oct 15 '12 at 05:33
  • 1
    Related: http://stackoverflow.com/questions/439791/what-is-the-most-efficient-thread-safe-c-logger – Dmitry Ledentsov Oct 15 '12 at 05:37

5 Answers5

8

Add a function for logging.
Both functions call the logging function (which does the appropriate locking).

mutex  logMutex;
void log(std::string const& msg)
{
    RAIILock  lock(logMutex);

    // open("abc.txt");
    // write msg
    // close
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • If performance is an issue, locking may be not something k0n3ru might want. – Dmitry Ledentsov Oct 15 '12 at 08:03
  • 1
    @devopsEMK: It is a generic reference to a class that uses `RAII` to Lock/Unlock another object. They are easy to write. An example is [`std::lock_guard`](http://en.cppreference.com/w/cpp/thread/lock_guard) – Martin York Jul 14 '16 at 14:54
2

If you really need a logger, do not try doing it simply by writing into files and perhaps use a dedicated logger, thus separating the concerns away from the code you're writing. There's a number of thread-safe loggers: the first one that comes to mind: g2log. Googling further you'll find log4cplus, a discussion here, even a minimalist one, +1

Community
  • 1
  • 1
Dmitry Ledentsov
  • 3,620
  • 18
  • 28
1

If the essence of functions foo_one() and foo_two() are only to open the file, write something to it, and close it, then use the same mutex to keep them from messing each other up:

void foo_one(){
  lock(foo_mutex);
  //open file abc.txt
  //write into file
  //close file
  unlock(foo_mutex);
}

void foo_two(){
  lock(foo_mutex);
  //open file abc.txt
  //write into file
  //close file
  unlock(foo_mutex);
}

Of course, this assumes these are the only writers. If other threads or processes write to the file, a lock file might be a good idea.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • Sadly , The functions foo_one() and foo_two() are in two different classes , and there is no way to have a shared data between them . – k0n3ru Oct 15 '12 at 05:30
  • 1
    @k0n3ru What about the lock file idea, then? There is a question that describes the method here: http://stackoverflow.com/questions/1599459/optimal-lock-file-method – jogojapan Oct 15 '12 at 05:36
1

You should do this, have a struct with a mutex and a ofstream:

struct parser {
    ofstream myfile
    mutex lock
};

Then you can pass this struct (a) to foo1 and foo2 as a void*

parser * a = new parser();

initialise the mutex lock, then you can pass the struct to both the functions.

void foo_one(void * a){
     parser * b = reinterperet_cast<parser *>(a);
     lock(b->lock);
         b->myfile.open("abc.txt");
         //write into file
         b->myfile.close();
      unlock(b->mutex);
}

You can do the same for the foo_two function. This will provide a thread safe means to write to the same file.

Fantastic Mr Fox
  • 32,495
  • 27
  • 95
  • 175
  • Although this approach 'works' and your answer is nicely written this is a place where you just need to know about keeping track of the mutex with RAII. – Johan Lundberg Oct 15 '12 at 05:37
0

Try this code. I've done this with MFC Console Application

#include "stdafx.h"
#include <mutex>

CWinApp theApp;
using namespace std;


const int size_ = 100; //thread array size
std::mutex mymutex;
void printRailLock(int id) {
    printf("#ID :%", id);
    lock_guard<std::mutex> lk(mymutex); // <- this is the lock
    CStdioFile lastLog;
    CString logfiledb{ "_FILE_2.txt" };
    CString str;
    str.Format(L"%d\n", id);
    bool opend = lastLog.Open(logfiledb, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);
    if (opend) {
        lastLog.SeekToEnd();
        lastLog.WriteString(str);
        lastLog.Flush();
        lastLog.Close();
    }
}
int main()
{
    int nRetCode = 0;
    HMODULE hModule = ::GetModuleHandle(nullptr);

    if (hModule != nullptr)
    {       
        if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
        {            
            wprintf(L"Fatal Error: MFC initialization failed\n");
            nRetCode = 1;
        }
        else
        {
            std::thread threads[size_];
            for (int i = 0; i < size_; ++i) {
                threads[i] = std::thread(printRailLock, i + 1);
                Sleep(1000);
            }
            for (auto& th : threads) { th.hardware_concurrency(); th.join(); }
        }
    }
    else
    {       
        wprintf(L"Fatal Error: GetModuleHandle failed\n");
        nRetCode = 1;
    }

    return nRetCode;
}

Referance:

Elshan
  • 7,339
  • 4
  • 71
  • 106