Modern C++: Multithreading and Filesystem library (practical example)
Tags: cpp, multithreading, coding, oop
Published: 2020-04-09 22:09:48


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](http://man7.org/linux/man-pages/man7/inotify.7.html) on Linux) that notifies processes when a file system event occurs.

### Definitions:

```cpp
//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  };
```

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 .
<hr>
### Implementation:

```cpp
//dirmgr.cpp

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

4 using namespace std;
5 namespace _NS_fs = std::filesystem;
```
<br>
#### 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.

```cpp
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 }
```
<hr>
_**start_watching**(string **path**)_**:** Creates an object of class **[thread](https://en.cppreference.com/w/cpp/header/thread)** on the [heap](https://stackoverflow.com/questions/2308751/what-is-a-memory-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](https://en.cppreference.com/w/cpp/language/this)_ 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).

```cpp
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 }
```
<hr>
_**stop_watching**()_**:** Just sets the data member **stop_flag** to true.

_**~DirManager**()_**:** The class destructor uses the **[join](https://en.cppreference.com/w/cpp/thread/thread/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](https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one), making it unnecessary to use the delete operator to release allocated memory.

```cpp
6 DirManager::~DirManager()
7 {
8    this->obj_linked_thd->join();
9    delete this->obj_linked_thd;
10 }
```
<hr>
#### 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.

```cpp
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 }
```
<hr>
_**count_items**(string **path**)_**:** Traverses all elements of the directory contained in **path** through the **_[directory_iterator](https://en.cppreference.com/w/cpp/filesystem/directory_iterator)_**. Each element **_p_** is an object of the **[directory_entry](https://en.cppreference.com/w/cpp/filesystem/directory_entry)** class. If **p** is a file (line 63), the counter is incremented.

```cpp
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 }
```
<hr>
The program entry point:

```cpp
//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 }
```
<hr>
### 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).
<hr>
Full repo and build info:
{% github prog-lessons/multithreading_fs_sample %}