Pelagia Getting Started Guide

introduce

Pelagia is a lock-free programming suite from surparallel.org. Use multi-threaded environment, embedded database, multi platform support, multi language support, etc. It provides a complete lock-free programming environment for beginners. Lock-free programming is a programming method which is implemented in multithreaded complex environment by using atomic lock queue and other methods. Traditional lockless programming requires users to master complex multithreading knowledge. Pelagia uses advanced technology from sequential virtual machines. So that users can develop multithreaded software with basic programming knowledge.

Environmental installation

Install on Linux system

Linux & Mac installation is very simple, just download the source package and decompress and compile it on the terminal. This article uses version 0.1 for installation:

 git clone https://github.com/surparallel/pelagia.git
 cd pelagia/src
 make linux

Install on Mac OS X

 git clone https://github.com/surparallel/pelagia.git
 cd pelagia/src
 make macosx

Install on window system

After decompression, enter the msvcs directory and open pelagia.sln for compilation.

Simple running example

Let's look at a simple example. How to use Pelagia in an embedded way in C language. You can find this example in simple. c under the directory src.

    static int TaskRouting(char* value, short valueLen) {
        void* pEvent = (void*)value;
    
        char table[10] = { 0 };
        strcpy(table, "table");
    
        char key[10] = { 0 };
        strcpy(key, "key");
    
        char v[100] = { 0 };
        strcpy(v, "hello world!");
    
        //Set data to data
        plg_JobSet(table, strlen(table), key, strlen(key), v, strlen(v) + 1);
        unsigned int len = 0;
    
        //get data
        void* ptr = plg_JobGet(table, strlen(table), key, strlen(key), &len);
        if (ptr) {
            printf("%s\n", (char*)ptr);
            free(ptr);
        }
    
        plg_EventSend(pEvent, NULL, 0);
        printf("-----------------job all pass!-----------------\n");
        return 1;
    }
    
    void plg_simple() {
    
        printf("\n-----------------manage create!-----------------\n");
        //Create system handle
        void* pManage = plg_MngCreateHandle(0, 0);
        //Create a message handle to receive messages
        void* pEvent = plg_EventCreateHandle();
        
        //Add a task to the system, including the name of the task and the callback function.
        char order[10] = { 0 };
        strcpy(order, "order");
        plg_MngAddOrder(pManage, order, strlen(order), plg_JobCreateFunPtr(TaskRouting));
    
        //Add the table to be written by the task. The read table can be read without adding
        //Each table is an independent key-value database
        char table[10] = { 0 };
        strcpy(table, "table");
        plg_MngAddTable(pManage, order, strlen(order), table, strlen(table));
    
        //Create resources used by threads and assign tasks to threads
        plg_MngAllocJob(pManage, 1);
        //Create thread environment
        plg_MngStarJob(pManage);
    
        //Trigger the task and use the event handle as a parameter
        plg_MngRemoteCall(pManage, order, strlen(order), (char*)&pEvent, sizeof(void*));
    
        //Wait for task return event
        plg_EventWait(pEvent);
    
        //Extract events from queue
        unsigned int eventLen;
        void * ptr = plg_EventRecvAlloc(pEvent, &eventLen);
        plg_EventFreePtr(ptr);
    
        //Finish
        plg_EventDestroyHandle(pEvent);
        plg_MngDestoryHandle(pManage, 0, 0);
        printf("\n-----------------manage destroy!-----------------\n");
    
    }

This example shows how to use Pelagia when you have a task, a table, and a thread. When you have multiple tasks and multiple tables that need to run on multiple threads. You can easily change to a multithreaded environment by modifying the parameter core in the plg_MngAllocJob. Pelagia will automatically assign your tasks to different threads.

Purpose of using Pelagia

As we all know, it is very difficult to develop a parallel software. First of all, there are many concepts and knowledge points of parallelism. Secondly, even if we master a large number of parallel concepts, the operation of parallel mode is very prone to errors. The reason for this error is that the execution of parallel software needs to coordinate a large number of sequence problems to ensure that the logic of execution is correct. This makes it difficult to avoid mistakes even if you are proficient in parallel knowledge. This kind of multi task coordination leads to errors in the form of accidental errors. The erratic way it happens makes it very difficult to deal with. In the environment of multi person development, this situation will become more difficult to control. So we rarely see complex parallel software.

How to make parallel software development easier, we need to start from two points. First, reduce the amount of knowledge to learn parallel software. Second, how to avoid people manually adjusting the execution order of software in parallel environment. So we use the concept of sequential virtual machine. The concept of sequential virtual machine is that users only need to master basic software development knowledge. In the virtual machine, we use a series of methods to let the user's software automatically adjust the execution order. By simulating the sequential execution environment, the threshold of parallel software development can be reduced. Reduce software parallel errors caused by human factors. What's more exciting is that the efficiency of software can be greatly improved by software parallelization.

The principle of sequential virtual machine is very simple. Software developed in any language consists of a large number of functions. These functions are executed in a particular order in a single threaded environment. This order depends on the environment and the user's execution request for the software. The input and output data of these functions are written to the shared area. Each function can read and write these shared data when it executes. We want to improve the running efficiency of single thread software, the simplest way is to split the software into multiple threads. Use sequential virtual machine according to preset function characteristics. Assign functions to different execution threads. And through each function to read and write the different needs of the data for appropriate allocation. Finally, different functions are run in different threads without changing the software structure. But in the end, how many copies of software can be distributed? It depends on the number of threads and the density of shared data used by the software.

To divide the maximum number of concurrent software, the most important thing is to see the software's reading and writing to the shared data area. So you can think of Pelagia as a system of multiple read-write locks. The system will create read-write locks according to the requirements of thread allocation. Therefore, each function should additionally mark the data it writes, as the basis for dividing system threads. The whole development process can be simply described as. Write the function, call the system interface storage for the data to be stored, call the system interface to describe the data written by the function, and then trigger the function execution.

Parallelization of software

Pelagia is a development tool that can parallelize software automatically. We usually use multithreaded development library to realize the parallel function of software. As described earlier, software parallel libraries are a very bad development experience. Every developer has its own implementation. It is difficult to develop, test and maintain. But the efficiency improvement brought by software parallelization is what every project is eager for. We all hope that the software can meet the needs of users in the shortest time. It began almost at the time of the birth of the computer. Numerous software engineers began to try to parallelize the software automatically. But they all ended in failure. Until the birth of Pelagia made a fundamental turning point.

The latest attempt to parallelize software is to use message mechanism. It is the way of message and function callback represented by go and nodejs. The callback function in the thread pool is activated by a message. A function usually performs three steps: reading data, calculating and writing data. Usually, such a development architecture is around functions and messages. The private data for each function is protected by the message mechanism. If we look at the process from a different perspective. Go back to sharing data between threads. It is also a process of multiple threads competing for shared data. It can be seen that in the automatic parallelization of software, data sharing between threads cannot be bypassed.

The way of bundling function and data in message mechanism reduces the difficulty of parallel software development to a certain extent. But it still can't solve the problem of granularity. With the increase of functions, a large amount of data that has been privatized and has not been privatized needs to be maintained. Whether and to what extent data will be privatized becomes difficult to control. Another problem emerges. With the increase of threads, the data contention between threads will become more and more obvious. Threads have to wait for each other to share data. The finer the data partition, the more difficult it is to control, and the coarser the data partition will cause threads to wait for each other. This software parallelization has entered a dilemma.

The problem of software parallelization comes down to data partition. In order to get the best parallel performance, we need to partition the data as detailed as possible. How to divide the data as detailed as possible. First of all, we need to understand the impact of function usage data process on software parallelization. People who have used git software will understand. The behavior of submitting code has a profound impact on data. It will affect all relevant developers. Data reading has no effect on other users. This is the AP&RP principle of data transmission process in parallelization. According to this principle, we can further divide the data into read data and written data. So we can break through the limit of current data division and further refine the data.

Each thread operates on shared data only when reading or writing data. It can maximize the use efficiency of shared data. Greatly reduce the waiting time between threads.

Because of the tag of using data behavior, we get whether the function has the attribute of writing data. Because writing data has a decisive influence on the parallelization of software. With this property, we can classify functions with write behavior. The classified function becomes a transaction safe function. Transaction security means that functions will not have thread deadlock and data confusion. Function after obtaining transaction security. We can further allocate the cache of data used by the function according to the data used by the function. Because the data used by the function is read from the hard disk. Huge hard disk data can not be extracted into memory. Therefore, when partitioning data in parallel environment, corresponding cache should be generated. Pelagia has completed a series of automatic configuration from function, data, thread, cache and hard disk. The user only needs to set the number of threads needed in the running state. Pelagia can automatically create, allocate and run all resources.

System overview

The whole system consists of four levels, management module, sequential virtual machine, cache, disk and other devices. The user calls the functions running in the sequential virtual machine through the management module. The function commits the relevant data to the cache when it executes. After the user completes the execution, the data is submitted to the hard disk or other devices by the cache. Functions executed within the virtual machine share data with each other through caching. Functions in the virtual machine can also call each other.

Although the system is composed of multiple sequential virtual machines, users do not need to know which virtual machine the function is running on. Although the system consists of multiple caches, users do not need to know which cache the data is stored in. As long as the user specifies the name of the running function, the system will help the user find the appropriate virtual machine to execute. As long as the user specifies the key of the data, the system will query the data in the specified cache and return it to the user. In the eyes of users, the software is like running in a single threaded environment. This environment consists of two parts: function calling system and data storage system. The whole system is asynchronous to improve efficiency, and there are several short-term and controllable synchronous ways to ensure the integrity of data. Users only need to follow the requirements of the system to set the way of using data. The system can help users to make appropriate thread allocation. There is no need for users to care about how to allocate and protect data in parallel mode.

Functional characteristics

1)Easy to use, easy to use, as long as basic programming knowledge can be easily used.

2)Proper use can greatly improve the efficiency of software operation.

3)Multiple isolation and protection of data can effectively prevent improper use of data in the process of team development.

4)Written in C language, it has powerful portability and can be applied to multiple platforms.

5)Support the extension of C, C + +, Lua and other languages.

6)The function of data processing is rich.

Concept definition

Virtual sequential environment: describes that the software developed by users can switch between parallel and sequential environments without any change.

Allocation tense: the process of analyzing user's software requirements and allocating resources when user software is not running.

Runtime: after resource allocation, start user software. Users are not allowed to change the software during runtime.

Data sharing, exclusive sharing, sharing, hard disk sharing: whether the data used by each function in user software can be accessed by other functions is called data sharing. Data that can only be used by the current function during this call is called exclusive data. Data that can be used by the current function or other functions during runtime is called shared data. Data recorded to the hard disk that can be used in multiple runtime States is called hard disk sharing.

Task call: trigger function in user software is called task call. Task invocation can be divided into two situations, one is outside the virtual sequential environment, the other is inside the virtual sequential environment.

Environment isolation: user tasks actually run in different threads, and scripting languages such as Lua even run in multiple LVMs.

Implicit transaction: each user task will not be formally submitted to the database until the end of operation. Task data at run time cannot be read by other tasks.

Read write decision: the system assigns threads, caches, and routes to user tasks according to the user's behavior of using data (read or write).

A detailed explanation of the concept

Virtual sequential environment is the most important concept in Pelagia system. The primary goal of Pelagia design is to switch order and concurrent environment without users' awareness. Users program and debug in sequential environment and run in concurrent environment. Users can continuously adjust the software channel to find the best performance according to the running situation.

In the actual operation process, it can be divided into distribution time and operation time. The allocation temporal takes the software developed by users as the analysis target. The analysis results are used to adjust the parameters of software parallel temporal. Users can set parameters in runtime according to these data. For the best parallel efficiency. Software is not inherently parallel. Whether it can be parallel or not is determined by the data reading and writing behavior of the software. The maximum number of software parallelizable in basic data depends on the division of software tasks. We usually divide software into multiple functions. But in theory, it is also possible to put all software functions in one function. When all software functions are put in one function, the maximum number of parallelizable software is 1. When the software is divided into multiple functions, the maximum number of parallelizable functions depends on the data reading and writing behavior of each function. If both functions write the same data, they cannot be parallel. If both functions only read the same data, then the two functions can be parallel.

Calls outside the virtual sequential environment in task calls will communicate through the event network and Io. Its form includes blocking and non blocking. Blocking mode will cause virtual machine to be stuck, and non blocking mode will result in data call failure. The system is not too involved in the behavior of whether users produce data blocking. Because blocking and non blocking are fuzzy in themselves. Blocking in seconds and blocking in milliseconds can be called blocking, but the result is totally different. It depends more on how the third party feels. In the future, the system may give suggestions in this regard. For example, some operating system functions may cause too long blocking time. Calling any operating system function is not currently prohibited.

We know that Pelagia is a parallel system composed of multiple threads. And support Lua and other scripting languages. Pelagia's approach is to create a language virtual machine within each thread. Instead of creating multiple threads within the language virtual machine. So it's called environmental isolation system. Note that this results in each language virtual machine being unable to share global data. This pattern obviously leads to the probability of low-level errors for novices. Since every language emphasizes the harm of using global data. It would also be useful to move this from a proposal to a rule.

Transactions are very important in parallel systems. It is a design to ensure data integrity and consistency. Pelagia adopts a strategy of implicit transaction. That is, the task execution period is considered as a complete transaction. When the task is completed, the transaction executed this time will be submitted automatically. The user can also choose to abort the commit or roll back.

Read and write data of user task is the only basis for judging task parallelism, also known as read and write decision. It may include multiple tasks writing the same data or multiple tasks reading the same data. It also includes multiple data written by one task or multiple data read by one task. Even a mixture of these. Including multiple data read or written by multiple tasks. In order to prevent users from getting into confusion, users only need to analyze the situation that each task reads and writes data separately during configuration. And use JSON or API for explanation. This is similar to the framework, which requires an additional specification for each task. It can be JSON or calling API directly.

Examples of finite elements

In the last simple example, we showed how to use Pelagia under 1 task, 1 table and 1 thread. Here we use a pseudo-random finite element simulation to show how to use Pelagia in a multithreaded environment. Finite element simulation is usually used to calculate the pressure model of building or machinery. For example, when a point in the building is affected by a force, it will spread to the surrounding points. This finite element simulation is used as an example.

First we create 10 points. When a point is affected by force, it will suffer a random damage. At the same time, another point is chosen randomly and a new force is generated until the damage value of a building is zero.

First create 10 point initialization task.

	for (int i = 0; i < 10; i++) {
	  
	    char order[10] = { 0 };  
	    sprintf(order, "i%d", i);  
	    plg_MngAddOrder(pManage, order, strlen(order), plg_JobCreateFunPtr(InitRouting));  
	  
	    char table[10] = { 0 };  
	    sprintf(table, "t%d", i);  
	    plg_MngAddTable(pManage, order, strlen(order), table, strlen(table));  
	}  
 

Then create a numeric manipulation function for 10 points.

	for (int i = 0; i < 10; i++) {  
	  
	    char order[10] = { 0 };  
	    sprintf(order, "o%d", i);  
	    plg_MngAddOrder(pManage, order, strlen(order), plg_JobCreateFunPtr(TestRouting));  
	  
	    char table[10] = { 0 };  
	    sprintf(table, "t%d", i);  
	    plg_MngAddTable(pManage, order, strlen(order), table, strlen(table));  
	}  
   

Then start the thread. Here we set the number of threads to 2.

	plg_MngPrintAllStatus(pManage);  
	plg_MngAllocJob(pManage, 2);  
	plg_MngPrintAllJobStatus(pManage);  
	plg_MngStarJob(pManage); 
  

Trigger initialization task.

	for (int i = 0; i < 10; i++) {  
	  
	    char order[10] = { 0 };  
	    sprintf(order, "i%d", i);  
	    Param param;  
	    param.i = i;  
	    param.pEvent = pEvent;  
	    plg_MngRemoteCall(pManage, order, strlen(order), (char*)¶m, sizeof(Param));  
	} 
  

The initialization of 10 points will be set to 100 in the initialization task.

	static int InitRouting(char* value, short valueLen) {  
	  
	    PParam pParam = (PParam)value;  
	    printf("---InitRouting--%d-\n", pParam->i);  
	      
	    char table[256] = { 0 };  
	    sprintf(table, "t%d", pParam->i);  
	  
	    short count = 100;  
	    plg_JobSet(table, strlen(table), "count", strlen("count"), &count, sizeof(short));  
	  
	    return 1;  
	}    
  

Then apply a force to each and trigger the action to deduct damage.

	for (int i = 0; i = 10; i++) {  
	  
	    char order[10] = { 0 };  
	    sprintf(order, "o%d", i);  
	    Param param;  
	    param.i = i;  
	    param.pEvent = pEvent;  
	    param.dmage = 1;  
	    plg_MngRemoteCall(pManage, order, strlen(order), (char*)¶m, sizeof(Param));  
	}
  

Before deducting the damage value, check whether there are zero damage points in all points. If there is a point with zero damage, stop transferring.

	for (int i = 0; i = 10; i++)  
	{  
	    short count;  
	    char table[10] = { 0 };  
	    sprintf(table, "t%d", i);  
	  
	    unsigned int len = 0, error = 1;  
	    void* ptr = plg_JobGet(table, strlen(table), "count", strlen("count"), &len);  
	  
	    if (ptr) {  
	        count = *(short*)ptr;  
	        free(ptr);  
	  
	        if (count = 0) {  
	            return 1;  
	        }  
	    }
  

Then get the damage value of the current point and cut off the damage. Then randomly find the next point and generate a new damage value. And pass it out through plg_JobRemoteCall.

	unsigned int len = 0, error = 1;  
	void* ptr = plg_JobGet(table, strlen(table), "count", strlen("count"), &len);  
	  
	if (ptr) {  
	    count = *(short*)ptr;  
	    free(ptr);  
	  
	    count -= pParam->dmage;    
	    plg_JobSet(table, strlen(table), "count", strlen("count"), &count, sizeof(short));  
	    if (count = 0) {  
	        //all pass  
	        plg_EventSend(pParam->pEvent, NULL, 0);  
	        printf("job all pass!\n");  
	    } else {  
	          
	        int c = rand()%10;  
	        if (pParam->i== 10) {  
	            c--;  
	        } else if (pParam->i == c) {  
	            c++;  
	        }  
	  
	        char order[10] = { 0 };  
	        sprintf(order, "o%d", c);  
	        pParam->i = c;  
	        pParam->dmage = rand() % 1?2:5;  
	        plg_JobRemoteCall(order, strlen(order), (char*)pParam, sizeof(Param));  
	    }  
	}  

We can run him to get results.

You can increase the running speed by adjusting the number of threads needed through the plg_MngAllocjob function.

Deadlock and Pelagia's task assignment principle

If you have rich experience in development, you will be very confused. How does Pelagia assign tasks to multiple threads without thread deadlock or other multithreading problems.

Consider the condition of deadlock yield. Suppose we have data a and data B. And there are task X and task y. So if task X and y are running in different threads respectively. Then x gets a and tries to access B, and Y gets B tries to access a. Then x, y will deadlock. At this time, if we put x, y back into a thread for serial execution, the deadlock problem will disappear. Pelagia is to execute tasks with deadlock possibility serially and tasks without deadlock possibility parallelly according to the situation of task usage data. We need to read and write data when we use it. Pelagia sees the need for the same data to be read and write as writing. When Pelagia's a, B data is used b y x, y tasks, the list of all possibilities for thread allocation is as follows.

So for the most part, Pelagia can assign multitasking and data systems to different threads. To improve the efficiency of the overall operation of the system.

Conclusion

Pelagia is a very interesting tool. Since the birth of computer system, we know little about parallel system. In the absence of sufficient theoretical and tool support. The development of parallel system is very difficult. Pelagia will help people develop parallel systems from a new perspective. You can download and try to run the example first. You can find us at surparallel.org or github.com if you have any problems.

Scroll to Top