Multithreading C calling the Python API trap

Source: Internet
Author: User
Tags fpm mutex python script

It is well known that a service (Wsgi interface) written in scripting languages requires a server container, common such as PHP php-fpm, LIGHTD, and so on. The uwsgi,uwsgi used in Python is a new protocol based on WSGI that can be used to deploy script programs such as Python. However, there may be some unexpected problems with developing a code architecture that is unfamiliar to UWSGI and the API of C calling Python.


Let's look at a piece of code, the following code is the flask frame, each request will be the value of the count is first minus again and again, and then multiply by two. If the request is 50 times, the final result should be 2 power of 50 times.


From flask import flask, request

COUNT = 1 

app = Flask (__name__)


@app. Route ('/test_uwsgi ')
def index ():
    Global COUNT
    Count=count-1
    count=count+1
    count=count*2
    print COUNT return
    ' OK '


17179869184
34359738368
68719476736
137438953472
274877906944
549755813888 1099511627776
2199023255552
4398046511104
8796093022208
17592186044416
35184372088832
70368744177664
140737488355328
281474976710656
562949953421312
1125899906842624


This is the result of the last few rows that are executed directly by the 50-second index function, and the result is 50 powers of 2.

536870912
 1073741824
2147483648
4294967296
8589934592
 17179869184 68719476736
137438953472
274877906944
549755813888
1099511627776
 2199023255552 4398046511104
8796093022208
17592186044416
 35184372088832
70368744177664
 140737488355328
281474976710656
5629499534213121125899906842624

This is the result of the last few lines obtained with multiple concurrent access to the/TEST_UWSGI interface through AB testing. It can be seen that the final result must be an anomaly number. Why does the program run in Uwsgi with an exception?

In fact, by reading this simple example, we can find that this example is commonly used to illustrate the problem of multithreading shared data synchronization, if no lock will expose the problem example. For the following code, we'll add a mutex when we modify the shared resource count to see if there are any changes.

From flask import flask, request
Import threading

Mutex = Threading. Lock ()
COUNT = 1 

app = Flask (__name__)


@app. Route ('/test_uwsgi ')
def index ():
    Global COUNT
    Global Mutex
    mutex.acquire ()
    count=count-1
    count=count+1
    count=count*2
    print COUNT
    Mutex.release () return
    ' OK '

The above code is also put into the Uwsgi container running, through the HTTP interface multiple concurrent access 50 times, the result is correct. But that's why. In our original Python code, we didn't write any operations involving multiple processes, although uwsgi in the configuration file enabled multiple threads to handle requests concurrently, but as I originally understood, it was not up to each thread to execute its own separate Python interpreter. The data for each thread when it runs the Python script should not be isolated.

To understand the above problems, we have to study the structure and design of UWSGI and its server architecture.

UWSGI is a server application container that is widely used in python, similar to the server application container for WSGI protocols that are common in PHP, such as mod-php, PHP-FPM, LIGHTD, and so on. The UWSGI agreement is to add a set of UWSGI agreements on top of the original WSGI agreement.


By studying Uwsgi's source code (CORE/UWSGI.C core/loop.c core/init.c core/master_util.c core/ UTIL.C), you can know Uwsgi's server design, the introduction of the Unx book introduces the induction of the server programming Paradigm 8, and TCP pre-create Thread server program, each thread accept.

int main (int argc, char *argv[], char *envp[]) {Uwsgi_setup (argc, argv, ENVP);
return Uwsgi_run ();

	} void Uwsgi_setup (int argc, char *argv[], char *envp[]) {int i;

	struct Utsname uuts; ......

	...
Set up and initialize all kinds of resources, here is omitted, interested in their own look ...///The main thing is this line of uwsgi_start (void *) UWSGI.ARGV);

	} int Uwsgi_start (void *v_argv) {... Simplified summary Some of the main code ...
		On the digression, here is a multithreaded, shared memory space that is used in the back uwsgi_setup_workers.

	Because Uwsgi has a master process that can monitor the state of each child process, an anonymous shared memory//Initialize Sharedareas Uwsgi_sharedareas_init () is required.
	Setup queue if (Uwsgi.queue_size > 0) {uwsgi_init_queue (); }

	... It's important here, UWSGI.P is an interface, the app deployed in Uwsgi is initialized here (in Uwsgi, the deployed app needs a plugin for the corresponding language, as Python does with Python), and the Python code that actually executes UWSGI, The import of all its modules is executed here//Initialize request plugin only if workers or master are available if (Uwsgi.sockets | | uwsgi.maste r_process | | Uwsgi.no_server | | Uwsgi.command_mode | |
			Uwsgi.loop) {for (i = 0; i < 256; i++) {if (uwsgi.p[i]->init) {uwsgi.p[i]->init (); }
		}
	}

	Again check for workers/sockets. if (uwsgi.sockets | | uwsgi.master_process | | uwsgi.no_server | | uwsgi.command_mo de | |
			Uwsgi.loop) {for (i = 0; i < 256; i++) {if (uwsgi.p[i]->post_init) {uwsgi.p[i]->post_init (); } } } 。。。

	This is mainly to set the shared memory space of each worker//Initialize Workers/master shared memory segments uwsgi_setup_workers (); Here we spawn the workers ... if (!UWSGI.STATUS.IS_CHEAP) {if (Uwsgi.cheaper && uwsgi.cheaper_count) {in
			T nproc = uwsgi.cheaper_initial;
			if (!nproc) Nproc = Uwsgi.cheaper_count;
					for (i = 1; I <= uwsgi.numproc. i++) {if (I <= nproc) {if (Uwsgi_respawn_worker (i)) break;
				Uwsgi.respawn_delta = Uwsgi_now ();
				else {uwsgi.workers[i].cheaped = 1; }} else {for (i = 2-uwsgi.master_process i < Uwsgi.numproc + 1; i++) {...
				This is to fork the child process if (Uwsgi_respawn_worker (i)) break according to the number of processes we set up;
			Uwsgi.respawn_delta = Uwsgi_now (); }
		}
	}


	// End of initialization return 0; int uwsgi_respawn_worker (int wid) {...

	Mainly this line of code, fork process, the inside is not with the pid_t pid = Uwsgi_fork (Uwsgi.workers[wid].name);
		if (PID = = 0) {signal (sigwinch, worker_wakeup);
		Signal (SIGTSTP, worker_wakeup);
		Uwsgi.mywid = WID;
		Uwsgi.mypid = Getpid ();
		PID is updated by the master//uwsgi.workers[uwsgi.mywid].pid = Uwsgi.mypid;
		Overengineering (just to be safe) uwsgi.workers[uwsgi.mywid].id = Uwsgi.mywid;
		   /* Uwsgi.workers[uwsgi.mywid].harakiri = 0;
		   Uwsgi.workers[uwsgi.mywid].user_harakiri = 0;
		   uwsgi.workers[uwsgi.mywid].rss_size = 0;
		 uwsgi.workers[uwsgi.mywid].vsz_size = 0;
		*//Do not reset worker counters on reload!!!
		uwsgi.workers[uwsgi.mywid].requests = 0;
		... but maintain a delta counter (yes racy in multithread)//uwsgi.workers[uwsgi.mywid].delta_requests = 0;
		uwsgi.workers[uwsgi.mywid].failed_requests = 0;
		uwsgi.workers[uwsgi.mywid].respawn_count++; Uwsgi.workers[uwsgi.mywid].last_spawn = Uwsgi.current_time;
	else if (PID < 1) {Uwsgi_error ("fork ()");

		else {//The PID is set only in the master, as the worker should never use it uwsgi.workers[wid].pid = pid;
		if (respawns > 0) {uwsgi_log ("respawned Uwsgi worker%d (new PID:%d) \ n", wid, (int) PID);
		else {Uwsgi_log ("spawned Uwsgi worker%d (PID:%d, cores:%d) \ n", WID, PID, uwsgi.cores);
} return 0; int Uwsgi_run () {... Also pick up important excerpt some if PID is master, execute Master_loop If PID is a worker, execute uwsgi_worker_run//!!!
	We could be in the master or in a worker!!!
	if (getpid () = = Masterpid && uwsgi.master_process = = 1) {(void) Master_loop (uwsgi.argv, Uwsgi.environ);
	//from now on the "process is" a real worker uwsgi_worker_run ();

Never here _exit (0);

	} void Uwsgi_worker_run () {int i;
	if (Uwsgi.lazy | | uwsgi.lazy_apps) {UWSGI_INIT_ALL_APPS ();

	} uwsgi_ignition ();

Never here exit (0); } void Uwsgi_ignition () {if (Uwsgi.loop) {void (*u_loop) (void) = Uwsgi_get_loop (Uwsgi.loop); if (!u_loop) {Uwsgi_log ("Unavailable loop engine!!!
			\ n ");
		Exit (1);
		} if (Uwsgi.mywid = = 1) {Uwsgi_log ("* * * running%s loop engine [addr:%p] ***\n", Uwsgi.loop, U_loop);
		} u_loop (); Uwsgi_log ("Your loop engine died.
	R.i.p.\n "); } else {...
		The loop body of the subprocess, generally using simple_loop if (Uwsgi.async < 1) {simple_loop ();
		else {async_loop ();
}//end of the process ... end_me (0); } 。。。 All the way here, in the loop of the subprocess. To create the execution function of the thread thread that receives processing request requests Simple_loop_run is also a cycle, basically is the regular step, accept,receive, response ..., We're not going to go after it. After Reciev the requested data, the WSGI function of the Python script is invoked through the Python_call method, processing the request void Simple_loop () {Uwsgi_loop_cores_run (
Simple_loop_run);
	} void Uwsgi_loop_cores_run (void * (*FUNC) (void *)) {int i;
		for (i = 1; i < uwsgi.threads; i++) {Long j = i;
	Pthread_create (&uwsgi.workers[uwsgi.mywid].cores[i].thread_id, &uwsgi.threads_attr, func, (void *) j);
	Long y = 0;
Func ((void *) y); }

In simple terms, it's different to execute a Python script in Uwsgi and run a Python script directly. Uwsgi executing a python script is by invoking the Python C API method, first loading the module in the Python script by invoking the API, at which point the relevant code in module import, like the first instance code, is executed. All global variables are created and initialized in the process. Then UWSGI creates the thread, starts processing the request to invoke the Python API (Python_call), executes the function that handles the request in the Python script (the Wsgi interface), because the module import was executed before the thread was created, So the shared data in the process was previously accessible in the thread. Here's what we need to focus on when accessing data shared between these threads requires locking, or when writing Python scripts as little as possible with global variables and using a single case pattern to avoid unnecessary pits.

In fact, the above mentioned is only a simplification of my problems, in order to help you understand the Uwsgi multithreaded implementation of the Python WSGI interface related issues. The problem I have encountered is that in the function of processing the request, a Gearman client created in the global is invoked, which is not thread safe and is not locked in use. When the requested concurrency is relatively large, the Gearman client library will report some connection exceptions.


Because of the Gil's presence, Python multithreading does not fully exploit the multi-core advantage. In the actual project, we often use multiple processes to replace the multithreaded approach to achieve some need concurrent business. However, after all, the process overhead is relatively large, and the design process data synchronization, interprocess communication and other operations relative to the thread is very complex, but also a pain in development, we often for performance and discard the use of Python to use the C multithreaded solution, but does reduce the code maintainability, Increases the cost of the code.

After understanding the multithreaded architecture of UWSGI, we can also learn how to invoke the Python C API through multithreading, using C's thread to invoke Python's business code, and put the data that needs to be shared before C-thread creation for module import. To extract the C-threaded portions into a framework, engineers simply write Python's business code and note that locks are added when using shared data. The framework is managed by a professional experienced C/python engineer, which improves the performance of the program without sacrificing the production efficiency of the Code.



PostScript: In fact, this problem is not very complex, exposing the problem is for the UWSGI code structure, and Python C API and C call Python methods and related concepts are not very skilled, exposing their knowledge system in the short board. Since those days at the same time in the development of several requirements, no detailed testing of the problem, not careful analysis and find errors in the trackback, but has been suspected of the caller's interface performance problems. In fact, the team in the communication does exist some problems, the argument is basically by shouting, rely on Shanghua, is not the matter, but often personal attacks. Whenever there is a real rationale for the engineer's plan, and when it does prove to be available, the opposing person would rather die than compromise ... This is probably like Sina such a bloated old lack of vitality of the big Company's common problems (a little spit, acquaintances please disregard).

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.