This article reprinted from headline number: www.toutiao.com/i6928959491…

Reproduced please indicate the source, thank you!

In a previous article (Essential Third-party Libraries for UNIX C), I introduced the basic functionality of Melon, a C library for Linux/UNIX, and gave a simple multi-process out-of-the-box example.

This article will show you how to use multithreading in Melon.

As mentioned in the previous article, there are two types of multithreading in Melon, one is a modular multithreading mode and the other is a thread pool, and we’ll give you examples one by one.

Melon’s Github address: github.com/Water-Melon…

Modular thread

Modular threading means that each thread is a separate code module with its own entry function (similar to the main function for each C program).

Modules are to be stored in Melon/ Threads /. Within the existing Melon code, two sample modules are included — haha and Hello (somewhat haphazardly named). Next, let’s take these two modules as examples to illustrate the development and use of modular threads.

The development process

Here are a few considerations:

  1. Module name: The module name will be used in two places, one in the configuration file and one in the module entry function name. The former will be explained in the usage process, and the latter will be explained in a moment using haha.

  2. Module parameters: The parameters are given in the configuration file, as we will explain in the usage flow. Note, however, that the last parameter is not given in the configuration file, but is appended automatically by the framework. It is the Socketpair socket that the main thread communicates with the thread module.

    / / haha module

    int haha_main(int argc, char **argv) { int fd = atoi(argv[argc-1]); mln_thread_msg_t msg; int nfds; fd_set rdset; for (;;) { FD_ZERO(&rdset); FD_SET(fd, &rdset); nfds = select(fd+1, &rdset, NULL, NULL, NULL); if (nfds < 0) { if (errno == EINTR) continue; mln_log(error, “select error. %s\n”, strerror(errno)); return -1; } memset(&msg, 0, sizeof(msg)); int n = read(fd, &msg, sizeof(msg)); if (n ! = sizeof(msg)) { mln_log(debug, “write error. n=%d. %s\n”, n, strerror(errno)); return -1; } mln_log(debug, “!!! src:%S auto:%l char:%c\n”, msg.src, msg.sauto, msg.c); mln_thread_clearMsg(&msg); } return 0; }

As you can see, in this example, the module’s entry function is named haha_main. For each thread module, their entry function is the name of their module (i.e., file name) + underline +main.

This example is also simple, using select to keep an eye on the main thread messages, log out when the messages are received from the main thread, and then release the resources.

The corresponding function is the hello module:

#include <assert.h> static void hello_cleanup(void *data) {mln_log(debug, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); } int hello_main(int argc, char **argv) { mln_thread_setCleanup(hello_cleanup, NULL); int i; for (i = 0; i < 1; ++i) { int fd = atoi(argv[argc-1]); mln_thread_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.dest = mln_string_new("haha"); assert(msg.dest); msg.sauto = 9736; msg.c = 'N'; msg.type = ITC_REQUEST; msg.need_clear = 1; int n = write(fd, &msg, sizeof(msg)); if (n ! = sizeof(msg)) { mln_log(debug, "write error. n=%d. %s\n", n, strerror(errno)); mln_string_free(msg.dest); return -1; } } usleep(100000); return 0; }Copy the code

The function of this module is also very simple, that is, to send a message to the main thread, and the receiver of the message is the HAHA module, that is, the main thread is a transfer station, which forwards the message of the Hello module to the HAHA module.

In the hello module, we call the mln_thread_setCleanup function, which is called to clean up custom resources after returning from the entry function of the current thread module.

Each thread module cleaning function is set only one of many Settings will be overwritten, cleaning function were independent of the thread, so there will be no thread processing function is to cover other (of course, you can also deliberately so to construct, such as send a deal with a function pointer to other modules, and then the module set).

Using the process

The use process follows the following steps:

  1. Write a framework launcher
  2. Compile links to generate executable programs
  3. Modifying a Configuration File
  4. Start the program

We proceed step by step:

For more information on how to install the repository, see the Github repository instructions or the previous article.

Let’s write the initiator first:

//launcher.c

#include "mln_core.h"

int main(int argc, char *argv[])
{
    struct mln_core_attr cattr;
    cattr.argc = argc;
    cattr.argv = argv;
    cattr.global_init = NULL;
    cattr.worker_process = NULL;
    return mln_core_init(&cattr);
}
Copy the code

Here, we don’t initialize any global variables, and we don’t need a worker process, so we just leave them empty.

$ cc -o launcher launcher.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -lpthread
Copy the code

Generate an executable program named Launcher.

At this point, our thread cannot execute, we need to modify the configuration file:

log_level "none"; //user "root"; daemon off; core_file_size "unlimited"; //max_nofile 1024; worker_proc 1; thread_mode off; framework off; log_path "/usr/local/melon/logs/melon.log"; /* * Configurations in the 'exec_proc' are the * processes which are customized by user. * * Here is an example to show you how to * spawn a program. * keepalive "/tmp/a.out" ["arg1" "arg2" ...]  * The command in this example is 'keepalive' that * indicate master process to supervise this * process. If process is killed, master process * would restart this program. * If you don't want master to restart it, you can * default "/tmp/a.out" ["arg1" "arg2" ...]  * * But you should know that there is another * arugment after the last argument you write here. * That is the file descriptor which is used to * communicate with master process. */ exec_proc { // keepalive "/tmp/a"; } thread_exec { // restart "hello" "hello" "world"; // default "haha"; }Copy the code

The above is the default configuration file, we need to make the following changes:

  • thread_mode off; -> thread_mode on;
  • framework off; -> framework on;
  • Two items in the thread_exec configuration block are uncommented

Here, an additional note is needed:

The thread_exec configuration block is designed to modularize threads, and each configuration item within it is a thread module.

Take hello as an example:

restart "hello" "hello" "world";
Copy the code

Restart or default is the instruction to restart a thread after it has exited the main function. Default means that once you exit, it will not start again. The following Hello string is the name of the module, and the rest is the module arguments, the argc and argv parts of the entry function. The socket that communicates with the main thread does not need to be written here, but is added automatically after the thread starts before entering the entry function.

Now, let’s start the program.

$ ./launcher Start up worker process No.1 Start thread 'hello' Start thread 'haha' 02/14/2021 04:07:48 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!! src:hello auto:9736 char:N 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0. 02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit. 02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!! src:hello auto:9736 char:N 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0. 02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit. 02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0...Copy the code

As you can see, Melon actually starts worker processes to pull up its children, and the number of worker processes is controlled by the worker_PROC configuration item. If there is more than one worker process, each worker process will pull up a set of haha and Hello threads. In addition, we also see that the cleanup function is called after the Hello thread exits.

The thread pool

The use of thread pools is largely framework-neutral, and is all about calling wrapped functions.

Here we restore the configuration file to the default configuration when it was installed.

Let’s look at a simple example:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "mln_core.h"
#include "mln_thread_pool.h"
#include "mln_log.h"

static int main_process_handler(void *data);
static int child_process_handler(void *data);
static void free_handler(void *data);

int main(int argc, char *argv[])
{
    struct mln_core_attr cattr;
    struct mln_thread_pool_attr tpattr;

    cattr.argc = argc;
    cattr.argv = argv;
    cattr.global_init = NULL;
    cattr.worker_process = NULL;
    if (mln_core_init(&cattr) < 0) {
        return -1;
    }

    tpattr.dataForMain = NULL;
    tpattr.child_process_handler = child_process_handler;
    tpattr.main_process_handler = main_process_handler;
    tpattr.free_handler = free_handler;
    tpattr.condTimeout = 10;
    tpattr.max = 10;
    tpattr.concurrency = 10;
    return mln_thread_pool_run(&tpattr);
}

static int child_process_handler(void *data)
{
    mln_log(none, "%s\n", (char *)data);
    return 0;
}

static int main_process_handler(void *data)
{
    int n;
    char *text;

    while (1) {
        if ((text = (char *)malloc(16)) == NULL) {
            return -1;
        }
        n = snprintf(text, 15, "hello world");
        text[n] = 0;
        mln_thread_pool_addResource(text);
        usleep(1000);
    }
}

static void free_handler(void *data)
{
    free(data);
}
Copy the code

The Melon framework is initialized first in the main function, mainly to initialize the log, since the framework will not be enabled in the configuration file. Then initialize the thread pool.

Program function is relatively simple, the main thread to create resources and then distribute resources, child threads get resources and log output.

All resource distribution and resource contention are encapsulated in the function. The callback function only needs to do functional logic processing.

The thread pool is initialized to allow a maximum of 10 children to be processed at the same time. If one of the current children is idle for more than 10 seconds, it will be reclaimed.

Here we generate an executable program and execute it:

$ cc -o hello hello.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -lpthread
$ ./hello
hello world
hello world
hello world
hello world
...
Copy the code

At this point, execute

$ top -d 1 -H -p PID
Copy the code

If PID is the process ID of the Hello program, you will see that, occasionally, two threads are present (this may not be seen if the machine is performing better, so you can shorten the usLEEP time).

Thanks for reading, and please leave a comment.

Once again, Melon’s official QQ group is given: 756582294

Github repository: github.com/Water-Melon…