Use Python for concurrent programming

Source: Internet
Author: User
Tags hypot imap rabbitmq
Concurrent running of computer programs is a frequently discussed topic. today I want to discuss various concurrency methods in Python. Concurrent running of computer programs is a frequently discussed topic. today I want to discuss various concurrency methods in Python.

Concurrency

Thread)

Multithreading is almost every programmer first comes up with a tool used to solve concurrency when using every language (JavaScript programmers should avoid it ), multithreading can effectively use CPU resources (except Python ). However, the complexity of the program caused by multithreading is inevitable, especially the synchronization of competing resources.

However, due to the use of the global interpretation lock (GIL) in python, the code cannot run concurrently on multiple cores. that is to say, the multi-thread in Python cannot run concurrently, many people will find that the program's running efficiency is reduced when multiple threads are used to improve their Python code. what a headache this is! For more details, read this article. In fact, it is very difficult to use multi-threaded programming models. programmers are prone to mistakes. this is not a programmer's mistake, because parallel thinking is against humanity, most of us think serially (not discussed in split spirits), and the computer architecture designed by von noiman is also based on sequential execution. So if you cannot solve your multi-threaded program, congratulations, you are a normal-Thinking Program :)

Python provides two sets of thread interfaces, one of which is the thread module. It provides basic, Low-Level interfaces and uses functions as the thread runtime bodies. Another group is the threading module, which provides object-based interfaces (similar to java) that are easier to use. it can inherit Thread objects to implement threads and provide other Thread-related objects, for example, Timer, Lock

Example of using thread module

1

2

3

4

5

Import thread

Def worker ():

"Thread worker function """

PRint 'worker'

Thread. start_new_thread (worker)

Example of threading module

1

2

3

4

5

6

Import threading

Def worker ():

"Thread worker function """

Print 'worker'

T = threading. Thread (target = worker)

T. start ()

Or Java Style

1

2

3

4

5

6

7

8

9

10

Import threading

Class worker (threading. Thread ):

Def _ init _ (self ):

Pass

Def run ():

"Thread worker function """

Print 'worker'

T = worker ()

T. start ()

Process)

Due to the global interpretation lock problem mentioned above, a better parallel method in Python is to use multiple processes, which can effectively use CPU resources and achieve real concurrency. Of course, the process overhead is higher than the thread, that is to say, if you want to create an astonishing number of concurrent processes, you need to consider whether your machine has a powerful heart.

Python's mutliprocess module and threading have similar interfaces.

1

2

3

4

5

6

7

8

From multiprocessing import Process

Def worker ():

"Thread worker function """

Print 'worker'

P = Process (target = worker)

P. start ()

P. join ()

Since threads share the same address space and memory, it is very easy to communicate between threads. However, communication between processes is more complicated. Common inter-process communications include pipelines, message queues, and Socket interfaces (TCP/IP.

Python's mutliprocess module provides encapsulated pipelines and queues for convenient message transmission between processes.

The use of locks for synchronization between Python processes is the same for drinking threads.

In addition, Python provides a process Pool object to conveniently manage and control threads.

Distributed Node)

With the advent of the big data era, Moore's theorem seems to have lost its effect on a single machine. data computing and processing require distributed computer networks to run, the concurrent running of programs on multiple host nodes is now an issue that must be considered in the current software architecture.

There are several common methods for inter-process communication between remote hosts.

TCP/IP

TCP/IP is the basis for all remote communication. However, the API is relatively low and complicated to use, so it is generally not considered

Remote Method Call

RPC is an early means of communication between remote processes. In Python, there is an open-source implementation of RPyC.

Remote Object

Remote objects are more advanced encapsulation. a program can operate a remote object on a local proxy like local objects. The remote object is the most widely used specification of CORBA. The biggest advantage of CORBA is that it can communicate in different languages and platforms. When there are some remote object implementations for unused languages and platforms, such as Java RMI and ms dcom

Python's open-source implementation supports many remote objects.

Dopy

Fnorb (CORBA)

ICE

OmniORB (CORBA)

Pyro

YAMI

Message Queue

Messages are more flexible communication methods than RPC or remote objects. common message mechanisms that support Python interfaces include:

RabbitMQ

ZeroMQ

Kafka

Aws sqs + BOTO

There is no major difference between concurrent execution on the remote host and local multi-process execution, and the problem of inter-process communication needs to be solved. Of course, managing and coordinating remote processes is more complex than local processes.

Python has many open-source frameworks to support distributed concurrency and provides effective management methods including:

Celery

Celery is a very mature Python distributed framework that can execute tasks asynchronously in distributed systems and provide effective management and scheduling functions. Refer to here

SCOOP

SCOOP (Scalable COncurrent Operations in Python) provides an easy-to-use distributed calling interface that uses the Future interface for concurrency.

Dispy

Compared with Celery and SCOOP, Dispy provides more lightweight distributed parallel services.

PP

PP (Parallel Python) is another Lightweight Python Parallel service. For more information, see

Asyncoro

Asyncoro is another Python framework that uses Generator to implement distributed concurrency,

Of course there are many other systems that I have not listed one by one

In addition, many distributed systems provide support for Python interfaces, such as Spark.

Pseudo-Thread)

Another method of concurrency is not common. we can call it a pseudo thread, which looks like a thread. the interfaces used are similar to the thread interface, but the non-thread method is actually used, the corresponding thread overhead does not exist.

Greenlet

Greenlet provides lightweight coroutines to support intra-process concurrency.

Greenlet is a by-product of Stackless. tasklet is used to support a technology called mirco-thread. here is an example of a pseudo thread using greenlet.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

From greenlet import greenlet

Def test1 ():

Print 12

Gr2.switch ()

Print 34

Def test2 ():

Print 56

Gr1.switch ()

Print 78

Gr1 = greenlet (test1)

Gr2 = greenlet (test2)

Gr1.switch ()

Run the above program and get the following results:

1

2

3

12

56

34

The pseudo thread gr1 switch will print 12, then call gr2 switch to get 56, then switch back to gr1, print 34, then the pseudo thread gr1 ends, the program exits, so 78 will never be printed. Through this example, we can see that using a pseudo thread, we can effectively control the execution process of the program, but the pseudo thread does not have real concurrency.

Eventlet, gevent, and concurence provide concurrency based on greenlet.

Eventlet http://eventlet.net/

Eventlet is a Python library that provides concurrent network calls. you can call blocked IO operations in a non-blocking manner.

1

2

3

4

5

6

7

8

9

10

11

12

Import eventlet

From eventlet. green import urllib2

Urls = ['http: // www.google.com ', 'http: // www.example.com', 'http: // www.python.org ']

Def fetch (url ):

Return urllib2.urlopen (url). read ()

Pool = eventlet. GreenPool ()

For body in pool. imap (fetch, urls ):

Print ("got body", len (body ))

The execution result is as follows:

1

2

3

('Got body', 17629)

('Got body', 1270)

('Got body', 46949)

Eventlet modified urllib2 to support the generator operation. The interface is consistent with urllib2. The GreenPool is the same as the Python Pool interface.

Gevent

Gevent is similar to eventlet. for details about their differences, refer to this article.

1

2

3

4

5

6

7

Import gevent

From gevent import socket

Urls = ['www .google.com ', 'www .example.com', 'www .python.org ']

Jobs = [gevent. spawn (socket. gethostbyname, url) for url in urls]

Gevent. joinall (jobs, timeout = 2)

Print [job. value for job in jobs]

The execution result is as follows:

1

['192. 169.145.226 ', '93. 184.216.34', '23. 235.39.223 ']

Concurence https://github.com/concurrence/concurrence

Concurence is another open-source library that uses greenlet to provide network concurrency. I have never used it. you can try it on your own.

Practical application

Concurrency is usually used in two scenarios. one is computing-intensive, that is, your program requires a large amount of CPU resources; the other is IO-intensive, the program may have a large number of read/write operations, including reading and writing files, sending and receiving network requests, and so on.

Computing-intensive

For computing-intensive applications, we use the famous Monte Carlo algorithm to calculate PI values. The basic principles are as follows:

The Monte Carlo algorithm uses statistical principles to simulate the calculation of the circumference rate. in a square, the probability of a random point falling in the area of 1/4 circle (red point) is proportional to its area. In this case, the probability p = Pi * R/4: R * R, where R is the side length of the square and the radius of the circle. That is to say, this probability is 1/4 of the circumference rate. using this conclusion, we can know the circumference rate as long as we simulate the probability that the vertex falls on the 1/4 circle. to obtain this probability, we can use a large number of experiments, that is, to generate a large number of points, to see which area of the point, and then calculate the results.

The basic algorithm is as follows:

1

2

3

4

5

From math import hypot

From random import random

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Here, the test method performs n (tries) tests and returns the number of points falling in the 1/4 circle. The determination method is to check the distance from the point to the center of the circle. if it is smaller than R, it is on the circle.

Through a large number of concurrency, we can quickly run multiple tests. the more times the test is performed, the closer the result is to the actual circumference rate.

The program code for different concurrent methods is provided here.

Non-concurrent

We should first run the process in a single thread to see how the performance works.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

From math import hypot

From random import random

Import eventlet

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Result = map (test, [tries] * nbFutures)

Ret = 4. * sum (result)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

Print calcPi (3000,4000)

Multi-thread

To use the thread pool, we use the dummy package of multiprocessing, which is an encapsulation of multithreading. Note that the code here does not mention threads, but it is indeed multi-threaded.

By testing the jing core (ya), we found that, as expected, when the thread pool is 1, its running results are the same as when there is no concurrency, when we set the number of the thread pool to 5, the time consumed is almost twice that of no concurrency. my test data ranges from 5 seconds to 9 seconds. Therefore, we recommend that you discard multithreading for computing-intensive tasks.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

From multiprocessing. dummy import Pool

From math import hypot

From random import random

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

P = Pool (1)

Result = p. map (test, [tries] * nbFutures)

Ret = 4. * sum (result)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

If _ name _ = '_ main __':

P = Pool ()

Print ("pi = {}". format (calcPi (3000,400 0 )))

Multi-process multiprocess

Theoretically, it is more appropriate to use multi-process concurrency for computing-intensive tasks. in the following example, set the size of the process pool to 5, and modify the size of the process pool to see the impact on the results, when the process pool is set to 1, the time required for multi-thread results is similar, because there is no concurrency at this time. when set to 2, the response time has been significantly improved, I didn't have half of the concurrency before. However, continuing to expand the process pool has little impact on performance or even a slight decrease. maybe my Apple Air CPU has only two cores?

Be careful, if you set a very large process pool, you will encounter the Resource temporarily unavailable error, the system does not support creating too many processes, after all, resources are limited.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

From multiprocessing import Pool

From math import hypot

From random import random

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

P = Pool (5)

Result = p. map (test, [tries] * nbFutures)

Ret = 4. * sum (result)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

If _ name _ = '_ main __':

Print ("pi = {}". format (calcPi (3000,400 0 )))

Gevent (pseudo thread)

Whether it is gevent or eventlet, because there is no actual concurrency, the response time is not much different from no concurrency, which is consistent with the test result.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

Import gevent

From math import hypot

From random import random

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Jobs = [gevent. spawn (test, t) for t in [tries] * nbFutures]

Gevent. joinall (jobs, timeout = 2)

Ret = 4. * sum ([job. value for job in jobs])/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

Print calcPi (3000,4000)

Eventlet (pseudo thread)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

From math import hypot

From random import random

Import eventlet

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Pool = eventlet. GreenPool ()

Result = pool. imap (test, [tries] * nbFutures)

Ret = 4. * sum (result)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

Print calcPi (3000,4000)

SCOOP

The Future interface in SCOOP complies with the PEP-3148 definition, that is, the Future interface provided in Python3.

In the default SCOOP configuration environment (single machine with four workers), the concurrent performance is improved, but not as good as the multi-process configured in the two process pools.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

From math import hypot

From random import random

From scoop import futures

Import time

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Expr = futures. map (test, [tries] * nbFutures)

Ret = 4. * sum (expr)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

If _ name _ = "_ main __":

Print ("pi = {}". format (calcPi (3000,400 0 )))

Celery

Task code

1

2

3

4

5

6

7

8

9

10

11

From celery import Celery

From math import hypot

From random import random

App = Celery ('task', backend = 'amqp ', broker = 'amqp: // guest @ localhost //')

App. conf. CELERY_RESULT_BACKEND = 'DB + sqlite: // results. sqlite'

@ App. task

Def test (tries ):

Return sum (hypot (random (), random () <1 for _ in range (tries ))

Client code

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

From celery import group

From tasks import test

Import time

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Result = group (test. s (tries) for I in xrange (nbFutures) (). get ()

Ret = 4. * sum (result)/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

Print calcPi (3000,400 0)

The result of the concurrent test using Celery is unexpected (the environment is single machine, 4 frefork concurrency, and the message broker is rabbitMQ), which is the worst of all test cases, the response time is no concurrent 5 ~ Six times. This may be because the overhead of control and coordination is too large. Celery may not be a good choice for such computing tasks.

Asyncoro

The test results and non-concurrency of Asyncoro are consistent.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Import asyncoro

From math import hypot

From random import random

Import time

Def test (tries ):

Yield sum (hypot (random (), random () <1 for _ in range (tries ))

Def calcPi (nbFutures, tries ):

Ts = time. time ()

Coros = [asyncoro. Coro (test, t) for t in [tries] * nbFutures]

Ret = 4. * sum ([job. value () for job in coros])/float (nbFutures * tries)

Span = time. time ()-ts

Print "time spend", span

Return ret

Print calcPi (3000,4000)

IO-intensive

IO-intensive tasks are another common use case. for example, a WEB server is an example of how many requests can be processed per second.

The simplest example is webpage reading.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

From math import hypot

Import time

Import urllib2

Urls = ['http: // www.google.com ', 'http: // www.example.com', 'http: // www.python.org ']

Def test (url ):

Return urllib2.urlopen (url). read ()

Def testIO (nbFutures ):

Ts = time. time ()

Map (test, urls * nbFutures)

Span = time. time ()-ts

Print "time spend", span

TestIO (10)

I will not list the code in different concurrent libraries because it is similar. You can refer to the computing-intensive code for reference.

Through tests, we can find that the use of multi-thread or multi-process for IO-intensive tasks can effectively improve the program efficiency, while the use of pseudo-thread performance improves significantly, when eventlet is less concurrent, the response time increases from 9 seconds to 0.03 seconds. At the same time, eventlet/gevent provides a non-blocking Asynchronous call mode, which is very convenient. We recommend that you use a thread or pseudo-thread because the thread and pseudo-thread consume less resources when the response time is similar.

Summary

Python provides different concurrency methods, which correspond to different scenarios. we need to select different methods for concurrency. If you select an appropriate method, you should not only understand the principles of the method, but also perform some tests and tests. data is the best reference for your selection.

The above is the content of concurrent programming using Python. for more related articles, please follow the PHP Chinese network (www.php1.cn )!

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.