DEV Community

Cover image for Modern C++: Multithreading and Filesystem library (practical example)
The Polymorphic Guy
The Polymorphic Guy

Posted on • Updated on

Modern C++: Multithreading and Filesystem library (practical example)

In this post, I will show you a bit of usage of multithreaded programming (C++11) and the Filesystem library (C++ 17). The idea is to get straight to the point.

Disclaimer: This is just a hypothetical approach that simulates a timer to check for any new files in a directory at regular intervals. The recommended approach for the real scenario would be to use some API from the OS itself (such as inotify on Linux) that notifies processes when a file system event occurs.

Definitions:

//dirmgr.hpp

1  #include <string>
2  #include <thread>

3  using namespace std;

4  class DirManager
5  {
6      private:
7          void watching_task(string path);
8          bool stop_flag = false;
9          int count_items(string path);
10         thread* obj_linked_thd;

11      public:
12          ~DirManager();
13          static void check_dirs();
14          bool start_watching(string path);
15          void stop_watching();
16  };
Enter fullscreen mode Exit fullscreen mode

First of all, I defined a class (DirManager), which will fulfill two roles: 1) To check the existence of the [in] and [out] directories in the current directory where the program was started from and 2) To monitor the [in] directory .


Implementation:

//dirmgr.cpp

1 #include "dirmgr.hpp"
2 #include <filesystem>
3 #include <iostream>

4 using namespace std;
5 namespace _NS_fs = std::filesystem;
Enter fullscreen mode Exit fullscreen mode

Public Methods

check_dirs(): Fulfills the role mentioned in 1). It was defined as static method, that is, it does not need to be invoked through an object, this can be done via class directly, such as at main.cpp:6.

11 void DirManager::check_dirs()
12 {
13    try
14    {
15       if (!(_NS_fs::exists("in")))
16          _NS_fs::create_directory("in");

17       if (!(_NS_fs::exists("out")))
18          _NS_fs::create_directory("out");
19    }
20    catch (const exception &e)
21    {
22       cout << "Error on trying to create [in]/[out] directories." << endl;
23       cerr << e.what() << endl;
24       exit(EXIT_FAILURE);
25    }
26 }
Enter fullscreen mode Exit fullscreen mode


start_watching(string path): Creates an object of class thread on the heap and stores its address in the pointer obj_th. The class constructor used in this case takes 3 arguments as input: 1. the address of the method that will be executed in a new thread (DirManager :: watching_task in this case), 2. the pointer to the instance of the class that has the method (this pointer in this case), and 3. the argument used by the method (path).

The private member obj_linked_thd receives the contents of obj_th in order to be accessed in the class destructor (dirmgr.cpp:8).

40 bool DirManager::start_watching(string path)
41 {
42    try
43    {
44       thread* obj_th = new thread(&DirManager::watching_task, this, path);
45       this->obj_linked_thd = obj_th;
46       return true;
47    }
48    catch (const std::exception &e)
49    {
50       std::cerr << e.what() << '\n';
51       return false;
52    }
53 }
Enter fullscreen mode Exit fullscreen mode


stop_watching(): Just sets the data member stop_flag to true.

~DirManager(): The class destructor uses the join() method of the thread class. This causes the parent thread to wait for the child thread to finish before proceeding. Since the obj_dm (main.cpp:7) object will be dropped at the end of program execution, this ensures that the child thread has completed before exiting the program, avoiding problems.

Note: On line 9, the delete operator is used to release the allocated memory addressed by obj_linked_thd as it was allocated through the new operator (line 44). Since C++11, the standard library has been supported smart pointers, making it unnecessary to use the delete operator to release allocated memory.

6 DirManager::~DirManager()
7 {
8    this->obj_linked_thd->join();
9    delete this->obj_linked_thd;
10 }
Enter fullscreen mode Exit fullscreen mode

Private Methods

watching_task(string path): Initially, it uses the count_items() method to get the number of files in path before entering the loop in which monitoring will be active while stop_flag is false. The thread is suspended inside the loop for 3 seconds (simulating a timer with this interval). Then the number of files is cheked; if it is larger than previous one, it means that a new file has been created.

27 void DirManager::watching_task(string path)
28 {
29    int files_count = this->count_items(path);

30    while (!this->stop_flag)
31    {
32       this_thread::sleep_for(chrono::seconds(3));

33       cout << "Monitoring [in] directory, interval 3 secs." << endl;

34       if (this->count_items(path) > files_count)
35          cout << "New file created." << endl;

36       files_count = this->count_items(path);
37    }

38    cout << "Monitor finished." << endl;
39 }
Enter fullscreen mode Exit fullscreen mode


count_items(string path): Traverses all elements of the directory contained in path through the directory_iterator. Each element p is an object of the directory_entry class. If p is a file (line 63), the counter is incremented.
58 int DirManager::count_items(string path)
59 {
60    int items = 0;

61    for (auto& p : _NS_fs::directory_iterator(path))
62    {
63       if (_NS_fs::is_regular_file(p.path()))
64          items++;
65    }
66    return items;
67 }
Enter fullscreen mode Exit fullscreen mode


The program entry point:
//main.cpp

1 #include "dirmgr.hpp"
2 #include <iostream>

3 using namespace std;

4 int main()
5 {
6    DirManager::check_dirs();
7    DirManager obj_dm;

8    if (!obj_dm.start_watching("./in"))
9    {
10       cout << "Could not start Directory Monitor." << endl;
11       return EXIT_FAILURE;
12   }

13   for (int n = 0; n <= 20; n++)
14   {
15       this_thread::sleep_for(chrono::seconds(2));
16       cout << "Main thread alive." << endl;
17   }

18   obj_dm.stop_watching();

19   cout << "Main thread finished." << endl;

20   return EXIT_SUCCESS;
21 }
Enter fullscreen mode Exit fullscreen mode

Execution:

https://dev-to-uploads.s3.amazonaws.com/i/2vrhfbdusr179s9kstux.png

In a terminal the program is launched. During its execution, in another terminal a file is created in the [in] directory. Then the message β€œNew file created.” is shown in the fisrt one.

It is important to note that in this multithreading use model, the execution is asynchronous, that is, one task does not need to wait for another to finish. Therefore, the ending message of the main thread is shown before the ending message of the child thread, although obj_dm.stop_watching() is invoked before the line where the ending message of the main thread is printed out (lines 18–19).



Full repo and build info:

GitHub logo prog-lessons / multithreading_fs_sample

Small sample program to demonstrate multithreading and filesystem lib usage in CPP17.



Top comments (0)