[C++ Rewriting Skynet Bottom Layer 01] Skynet Practical Combat-sunnet basic framework, create, open, wait for thread exit methods, imitate skynet to write message classes
[C++ Rewrite Skynet Bottom Layer 02] Skynet Practical Combat-Imitating Skynet to write service classes, object management under multi-threading, spin lock mutex program writing, hash table management objects, and summary of program running steps
[C++ Rewriting Skynet Bottom Layer 03] Skynet Practical Combat-Insertion and pop-up of global message queue, imitating Skynet to send messages, message transmission and message processing between services
[C++ Rewriting Skynet Bottom Layer 04] Skynet Practical Combat – Demonstration program PingPong, waiting and waking up of worker threads, improved scheduling function
Article directory
- Preface
- 1. Build the basic framework of Sunnet
- 2. How to enable multi-core and multi-threading in C++
-
- 2.1. Create thread object
- 2.2 Simulate skynet opening thread
- 2.3 Wait for the thread to exit
- 3. Actor system (imitation of skynet writing message class)
-
- 3.1 Message class of Actor model
- 3.2 Message base class
- 3.3 BaseMsg subclass ServiceMsg
- Summarize
Foreword
The bottom layer of skynet is written in C language, which provides a set of Actor model mechanisms. This series will use C++ to imitate skynet as the main line to learn operating system concepts such as “threads”, “locks”, and “condition variables”. Implement Sunnet scheduling function
The running environment of this series is a Linux environment. Please configure the relevant environment (g++, cmake, etc.) in advance. For specific configuration, please refer to a blog I wrote before:
[Linux c++ practical] Teach you step by step how to build a c++ project from scratch on Linux, and introduce Cmake in detail (C++|Cmake|Ubuntu)
The learning reference book for this series “Millions of Games: Large-Scale Game Server Development”
In this section, we will first build the basic framework of Sunnet, and use C++ to create threads, start threads, wait for threads to exit, and imitate skynet to write message classes.
1. Build the basic framework of Sunnet
First create an object, which is globally unique and can represent the entire system. An Actor system contains multiple services, and Sunnet::inst manages them, so in the first step we will implement the Sunnet::inst code first
//Sunnet.h file class Sunnet {<!-- --> public: //single case static Sunnet* inst; //Using singleton mode, the static variable inst is declared and assigned a value in the constructor public: //Constructor Sunnet(); //Initialize and start void Start(); };
//Sunnet.cpp #include <iostream> #include "Sunnet.h" using namespace std; //single case Sunnet* Sunnet::inst; Sunnet::Sunnet(){<!-- --> inst = this; } //Start the system void Sunnet::Start() {<!-- --> cout << "Hello Sunnet" << endl; }
//main.cpp #include "Sunnet.h" int main(){<!-- --> new Sunnet(); Sunnet::inst->Start(); //Some logic after turning on the system return 0; }
After building, compiling, and executing, the program can clearly see that “Hello Sunnet” is printed. Although it is a simple program here, the Sunnet framework has been set up, and development will be carried out on this basis.
2. How to enable multi-core and multi-threading in C++
2.1. Create thread objects
There are at least four methods of creating threads in C++ (For the introduction of the four creation methods, please refer to my summary), here we choose the “thread object” method. In Worker.h, we define the worker class, which will equip each thread with a worker object. The thread object has two attributes: id and eachnum for subsequent In the service scheduling function.
//Worker.h #pragma once #include <thread> #include "Sunnet.h" classSunnet; using namespace std; class Worker {<!-- --> public: int id; //number int eachNum; //How many messages are processed each time void operator()(); //Thread function };
operator()() is a thread execution method. It is an infinite loop. Each loop will print out the workingid and then block for 0.1s.
//worker.cpp #include <iostream> #include <unistd.h> #include "Worker.h" using namespace std; //thread function //void operator()() means overloading the bracket operator, which is the core content of the thread object. void Worker::operator()() {<!-- --> while(true) {<!-- --> cout << "working id:" <<id <<endl; usleep(100000) //0.1s } }
2.2 Simulate skynet opening thread
You only need the following two lines of code to create a thread, and then the thread will start running and printing continuously. The specific application code will be introduced later:
Worker* worker = new Worker(); // Create a worker object thread* wt = new thread(*worker); //Create a thread and specify its corresponding worker object
In order to allow Sunnet to manage threads, threads, and thread objects are all managed by Sunnet::inst, so member functions and member variables are added to Sunnet, among which:
workerThreads represents threads, and Sunnet::inst will automatically schedule three of them.
Each thread must carry a thread object (worker), so workers is also an array, storing 3 workers.
//Sunnet.h #pragma once #include <vector> #include "Worker.h" #include <unordered_map> using namespace std; class Worker; class Sunnet {<!-- --> public: //single case static Sunnet* inst; public: //Constructor Sunnet(); //Initialize and start void Start(); //wait to run void Wait(); //New //Add new private: //worker thread int WORKER_NUM = 3; //Number of working threads (configuration), you can set it yourself vector<Worker*> workers; //worker object vector<thread*> workerThreads; //Thread, a thread array (vector type) private: //Start the working thread void StartWorker(); };
Then go to the cpp file and write the corresponding function code StartWorker(), and Wait():
In StartWorker(), a worker object is first created each time, and its attributes are assigned values, and then the thread wt is created, and both wt and the thread object worker are added to the array managed by Sunnet.
//Sunnet.cpp //StartWorker(),Wait() definition //Start the worker thread void Sunnet::StartWorker() {<!-- --> for (int i = 0; i < WORKER_NUM; i + + ) {<!-- --> cout << "start worker thread:" << i << endl; //Create thread object Worker* worker = new Worker(); worker->id = i; worker->eachNum = 2 << i; //Create thread thread* wt = new thread(*worker); //Add to list workers.push_back(worker); workerThreads.push_back(wt); } } //wait void Sunnet::Wait() {<!-- --> if( workerThreads[0]) {<!-- --> workerThreads[0]->join(); } }
Sunnet::inst manages workerThreads and workers, and finally opens the system’s start method and calls StartWorker() in it.
//Sunnet.cpp //Start the system void Sunnet::Start() {<!-- --> cout << "Hello Sunnet" << endl; //Open Worker StartWorker(); //New }
The results after compiling and running are as follows:
2.3 Wait for the thread to exit
In 2.2 we defined the wait() function. The “join” method of the thread in the code can block the caller until the thread exits. If wait (main function) is called in the main thread, the main thread will wait for the first worker. thread (workerThreads[0]) until this thread exits, but the worker thread is in an infinite loop, excluding the outside. Therefore, the main thread will be permanently blocked.
//main.cpp int main(){<!-- --> new Sunnet(); Sunnet::inst->Start(); Sunnet::inst->Wait(); //New return 0; }
After running the code, each thread will continue to print the workingid, and since multiple threads operate at the same time, conflicts will inevitably occur (the content of the next thread will be printed before the previous thread has finished printing). Methods to resolve thread conflicts will be discussed later. .
3. Actor system (imitation of skynet writing message class)
3.1 Message class of Actor model
Actors need at least two objects: “service” and “message”. In addition to the global management of Sunnet::list mentioned in the previous section, the Actor system also needs to include service objects (threads) and message objects (Msg, communication between messages).
We first design the message class structure for Sunnet. The base class BaseMsg stores the general properties of the message. ServiceMsg is used for message transfer between services, and others are used for Socekt communication.
ServiceMsg
BaseMsg
xxxMsg
xxxMsg
3.2 Message base class
//Basic content of message base class (Msg.h):
//Msg.h //Basic content of message base class: #pragma once #include <memory> using namespace std; //Message base class class BaseMsg {<!-- --> public: enum TYPE {<!-- --> //Message type SERVICE = 1, }; uint8_t type; //Message type char load[999999]{<!-- -->}; //Used to detect memory leaks virtual ~BaseMsg(){<!-- -->}; };
3.3 BaseMsg subclass ServiceMsg
source represents the sender, recording the sender’s service ID, buff refers to the message content, which is a smart pointer pointing to a char type array, and size represents the message length;
//Msg.h //Message between services class ServiceMsg : public BaseMsg {<!-- --> public: uint32_t source; //Message sender shared_ptr<char> buff; //Smart pointer, message content size_t size; //Message content size };
Summary
In this article, we first build the basic framework of Sunnet, then write C++ methods to create threads, start threads, wait for threads to exit, and finally imitate skynet to write message classes. In subsequent studies, we will continue to write content related to queues and locks, imitating skynet. Write service classes, and introduce the use of spin locks, object management under multi-threading, how to make full use of the CPU, etc.
To be continued…