Skip to content

Instantly share code, notes, and snippets.

@vishal-keshav
Last active January 26, 2019 12:58
Show Gist options
  • Save vishal-keshav/4404c6a1f2eea2a5bccb53fee7b8fa71 to your computer and use it in GitHub Desktop.
Save vishal-keshav/4404c6a1f2eea2a5bccb53fee7b8fa71 to your computer and use it in GitHub Desktop.
On Object oriented programming and design patterns in C++ part 6

Design Principles Part 6

In this part, we will learn more about single responsibility priniciple. To learn that, we can start with a problem described below.

Problem statement

Implement a simple concurrent file uploader. Allow the user to keep entering filename, whenever they want to upload. Each filename entered by the user should be uploaded to the FileServer which is remotely located over the internet. Each upload approximately takes 10 seconds time. When the upload for one file is going on, another should not be uploaded. Other files should be uploaded one after the other, the way it happens on google drive client or filezilla.

User should be allowed to enter filenames when upload is going on. No filename entered by the user should be missed. When each upload starts, display the getting uploaded and when upload is completed, dispaly "filename" uploaded.

Intial Implementation

#include "stdafx.h"
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <vector>

#include <Windows.h>

using namespace std;

class upload_util 
{
private:
	mutex sync;
public:
	void upload_file(string filename) {
		lock_guard<mutex> gaurd1(sync);
		cout << filename << " is getting uploaded" << endl;
		Sleep(10000);
		cout << filename << " uploaded" << endl;
	}
};

int main()
{
	upload_util u;
	string filename;
	while(true) {
		cout << "Enter file to upload" << endl;
		cin >> filename;
		thread t([&]() {u.upload_file(filename); });
		t.detach();
	}

	return 0;
}

There is one problem with this implementation. It creates one thread for each uploads, which is very inefficient. TO solve this, we can use a queue, to keep queuing file name as and when entered by user. And upload files by dequeuing from that single queue in one thread only.

With single thread and a queue.

#include "stdafx.h"
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <vector>
#include <queue>

#include <Windows.h>

using namespace std;

class Uploader 
{
private:
	thread t;
	queue<string> filenames;
public:
	Uploader() 
	{
		t = thread(bind(&Uploader::perform_upload, this)); // Callback pattern
	}
	void perform_upload()
	{
		while(true)
		{
			//TODO: if no file is there to upload, go to wait state/
			// and comeout of the wait state when there is something to upload
			string filename = filenames.front();
			filenames.pop();
			// Simulation of file upload
			cout << filename << " upload started" << endl;
			Sleep(10000);
			cout << filename << " upload completed" << endl;
		}
	} //when secondry thread rached the end of this function, it will die.

	void upload(const string& filename) {
		filenames.push(filename);
		cout << filename << " queued" << endl;
	}
};

class ConsoleUI  
{
private:
	Uploader uploader; // tight coupling with uploader
public:
	void start() {
		string filename;
		while(true) {
			cin >> filename;
			uploader.upload(filename);
		}
	}
};

int main()
{
	ConsoleUI cui; //tightly coupled with main
	cui.start();

	return 0;
}

In the above code, we observe that when uploader object was created, the thread was spawned and is constantly running in background, checking if there is anything in the queue, and then uploading it. While in the main thread, user can queue more file items concurrently.

This is still inefficient, since the uploader thread is always running in infinite loop and wasting cpu time. To handle this, it is advisable to make a thread wait for some event. This event will be the enqueue of file name from in the main thread. Once all the uploads are done, thread should again start waiting for the enqueue signal to wake up.

In the code below, this is solved by condition_variable. Moreover, there is one more problem. That is concurrent enqueue and dequeue of file name. In order to solve this, we can add a muted on the enqueue dequeue operations. adding locks everyrhing will clutter the code. Single responsibility principle says that one type of work should be handled by one class only.

Implementation with SRP

#include "stdafx.h"
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <vector>
#include <queue>

// 
#include <condition_variable>

#include <Windows.h>

using namespace std;

// Implementing thread-safe class
// This refactoring following SRP patterns
template<class T>
class concurrentQueue
{
private:
	queue<T> que;
	mutex sync;
public:
	void add(const T& item)
	{
		lock_guard<mutex> guard(sync);
		que.push(item);
	}
	T remove()
	{
		lock_guard<mutex> guard(sync);
		T elem = que.front();
		que.pop();
		return elem;
	}
	bool is_empty()
	{
		lock_guard<mutex> guard(sync);
		return que.empty();
	}
};

class Uploader 
{
private:
	thread t;
	concurrentQueue<string> filenames;
	condition_variable cv;
	mutex m;
public:
	Uploader() 
	{
		t = thread(bind(&Uploader::perform_upload, this)); // Callback pattern
	}
	void perform_upload()
	{
		while(true)
		{
			//condition_variable helps wait a thread in sleep mode
			// This makes thread go to wait state
			
			if(filenames.is_empty()) {
				cout << "Entering the wait state" << endl;
				unique_lock<mutex> lk(m);
				cv.wait(lk);
				cout << "Has come out of the wait state" << endl;
			}

			string filename = filenames.remove();
			// Simulation of file upload
			cout << filename << " upload started" << endl;
			Sleep(10000);
			cout << filename << " upload completed" << endl;
		}
	} //when secondry thread rached the end of this function, it will die.

	void upload(const string& filename) {
		filenames.add(filename);
		cout << filename << " queued" << endl;
		cv.notify_one(); //brings the waiting thread out of the wait state
	}
};

class ConsoleUI  
{
private:
	Uploader uploader; // tight coupling with uploader
public:
	void start() {
		string filename;
		while(true) {
			cin >> filename;
			uploader.upload(filename);
		}
	}
};

int main()
{
	ConsoleUI cui; //tightly coupled with main
	cui.start();

	return 0;
}
@vishal-keshav
Copy link
Author

To get rid of platform dependencies, remove cout and cin
Instead fire events with observer pattern.
These events will be UploadStarted, UploadComplete, NoFileLeftForUpload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment