Web Server vs Game Server
Web Server:
- small # of requests per user
- don’t need real-time interaction
- Server doesn’t approach client first
- Stateless - forgets user after request is done
- Usually uses web frameworks
- ex) drivethrough
Game Server:
- a lot of request/response
- real-time interaction
- stateful
- cannot use web framework (different needs for each game)
- ex) restaurant - constant service to the user
C++ Thread
#include <thread>
#include <thread>
int main()
{
	std::thread t;
	// CPU core number
	int t.hardware_concurrency();
	// thread id
	auto id = t.get_id();
	// detach the actual thread from std::thread, make it run independently as background thread
	t.detach();
	// check if the thread is valid and can join
	if (t.joinable()) {
		t.join();
	}
}
Atomic Type
all-or-nothing
- 
lock behavior for simple types
#include <atomic>
atomic<int> sum = 0;
- stl containers are probably not multi-thread safe.
Mutex
Basic C++ lock
#include <vector>
#include <atomic>
#include <mutex>
mutex m;
vector<int32> v;
void Push()
{
    for (int32 i = 0; i < 10'000; i++) {
        m.lock();
        v.push_back(i);
        m.unlock();
    }
}
int main()
{
    auto t1 = std::thread(Push);
    auto t2 = std::thread(Push);
    t1.join();
    t2.join();
    cout << v.size() << endl;
}
if there’s unusual jump before m.unlock(), deadlock happens
- we can’t be checking every branch and unlocking; too error-prone.
Types of Lock
- Spin Lock(Infinite wait)
- Sleep
- Event based lock
Lock Guard - RAII Pattern
// Wrapper class to automate lock acquisition/release
template<typename T>
class LockGuard
{
public:
	LockGuard(T& m)
	{
		_mutex = m;
		_mutex->lock();
	}
	~LockGuard()
	{
		_mutex->unlock();
	}
private:
	T* _mutex;
};
#include <vector>
#include <atomic>
#include <mutex>
mutex m;
vector<int32> v;
void Push()
{
    for (int32 i = 0; i < 10'000; i++) {
		// acquires lock on initialization
		auto lock_guard = LockGuard(m);
		// std C++ version. Same function as above.
		std::lock_guard<std::mutex> lock_guard(m);
		// lock_guard + doesn't lock instantly
		std::unique_lock<std::mutex> lock_guard_unique(m, std::defer_lock);
		lock_guard_unique.lock();
        v.push_back(i);
		// lock_guard is destroyed, automatically releasing lock(even if there's goto)
    }
}
SpinLock
- voilatile: don’t do compile optimization.
// in release, runs int a = 4 directly!
int a = 0;
a = 1;
a = 2;
a = 3;
a = 4;
// doesn't cut optimizations and run every line
volatile int a = 0;
a = 1;
a = 2;
a = 3;
a = 4;
Needs to be used for lock
class SpinLock {
public:
    void lock()
    {
        // Compare and swap
        bool expected = false;
        bool desired = true;
        while (_locked.compare_exchange_strong(expected, desired) == false) {
            expected = false;
        }
    }
    void unlock()
    {
        _locked.store(false);
    }
private:
    atomic<bool> _locked = false;
};
SleepLock
class SleepLock {
public:
    void lock()
    {
        // Compare and swap
        bool expected = false;
        bool desired = true;
        while (_locked.compare_exchange_strong(expected, desired) == false) {
            expected = false;
			this_thread::sleep_for(0ms);
        }
    }
    void unlock()
    {
        _locked.store(false);
    }
private:
    atomic<bool> _locked = false;
};
EventLock
Sleep until event occurs. Use window/linux system call
// GameServer.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include "AccountManager.h"
#include "UserManager.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <queue>
#include <windows.h>
mutex m;
queue<int32> q;
HANDLE handle;
void Producer()
{
    while (true)
    {
        unique_lock<mutex> lock(m);
        q.push(100);
    }
    // turns signal on
    ::SetEvent(handle);
    this_thread::sleep_for(100ms);
}
void Consumer()
{
    while (true)
    {
        ::WaitForSingleObject(handle, INFINITE);
        unique_lock<mutex> lock(m);
        if (q.empty() == false)
        {
            int32 data = q.front();
            q.pop();
            cout << data << endl;
        }
    }
}
int main()
{
    // Kernel Object
    // Usage Count, Signal on/off(bool), Auto/Manual(bool)
    handle = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    std::thread t1(Producer);
    std::thread t2(Consumer);
    t1.join();
    t2.join();
    ::CloseHandle(handle);
}
Condition Variable
The event above doesn’t actually give 1 to 1 mapping of Producer and Consumer:
- Another Producer thread can be called right after SetEvent before any Consumer is called
To guarantee an ordered approach, we need to use condition variable
- wakes up waiting thread on condition
- acquires lock if condition is met
// GameServer.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include "AccountManager.h"
#include "UserManager.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <queue>
#include <windows.h>
mutex m;
queue<int32> q;
condition_variable cv;
void Producer()
{
    while (true)
    {
        {
			unique_lock<mutex> lock(m);
			q.push(100);
        }
    }
    // wake only one waiting thread
    cv.notify_one();
    this_thread::sleep_for(100ms);
}
void Consumer()
{
    while (true)
    {
        unique_lock<mutex> lock(m);
        // why check empty()
        // Spurious Wakeup
        // We don't have the lock when we're waiting, so another consumer could use the queue simultaneously with notify_one.
        cv.wait(lock, []() { return q.empty() == false; });
        if (q.empty() == false)
        {
            int32 data = q.front();
            q.pop();
            cout << data << endl;
        }
    }
}
int main()
{
    // Kernel Object
    // Usage Count, Signal on/off(bool), Auto/Manual(bool)
    std::thread t1(Producer);
    std::thread t2(Consumer);
    t1.join();
    t2.join();
}
One-time only tasks
We can use future/promise/packaged_task
- future async: simplest
- promise: more control over start timing, set_exception
- packaged_task: used for managing big tasks that require multiple thread or thread pool.
// GameServer.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include "AccountManager.h"
#include "UserManager.h"
#include <iostream>
#include <future>
int64 Calculate()
{
	int64 sum = 0;
	for (int32 i = 0; i < 100'00; i++)
		sum += i;
	return sum;
}
void PromiseWorker(std::promise<string>&& promise)
{
	promise.set_value("Secret message");
}
void TaskWorker(std::packaged_task < int64(void)> && task)
{
	task();
}
int main()
{
	// std::future: midpoint btw thread and synchronous
	{
		// 1) deferred -> lazy evaluation in current thread
		// 2) async -> create separate thread and run
		// 3) deferred | async: choose any of the two
		std::future<int64> future = std::async(std::launch::async, Calculate);
		future.wait_for(0ms);
		int64 result = future.get();
	}
	// std::promise
	// more control over exactly when to start thread, can deliver exceptions
	{
		std::promise<string> promise;
		std::future<string> future = promise.get_future();
		thread t(PromiseWorker, std::move(promise));
		string message = future.get();
		cout << message << endl;
		t.join();
	}
	// std::packaged_task
	{
		std::packaged_task<int64(void)> task(Calculate);
		std::future<int64> future = task.get_future();
		std::thread t(TaskWorker, std::move(task));
		int64 sum = future.get();
		cout << sum << endl;
		t.join();
	}
}