0. 简介

线程和进程的区别:

进程是资源分配的最小单位,线程是CPU调度的最小单位。

也就是说,线程是在进程的概念下的,进程完成一个大的工作,线程则完成其中的小任务。进程间可能加互斥锁,而线程的内存则容易共享等。

Q1:为什么不推荐使用python的多线程
A1:对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。
在多线程环境中,Python虚拟机按照以下方式执行。
1.设置GIL。
2.切换到一个线程去执行。
3.运行。
4.把线程设置为睡眠状态。
5.解锁GIL。
6.再次重复以上步骤。

对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会在自己的时间片内一直占用处理器和GIL。也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。

Python不一样,**它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。**看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。

GIL

GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。

多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大

Q3:简述多线程、多进程

进程:

1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立

2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制

线程:

1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源

2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃

应用:

IO密集的用多线程,在用户输入,sleep 时候,可以切换到其他线程执行,减少等待的时间;

CPU密集的用多进程,因为假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势。

1. 多线程

# 多线程
import threading

def haha(max_num, k = 0, j = 3):
    for i in range(max_num + k + j):
        time.sleep(1)
        print(i)
    return i
for x in range(3):
    n = x + 3
    t = threading.Thread(target=haha,args=(n,1,))
    #也可以干脆不写这一行
    t.setDaemon(False)
    t.start()

2. 多进程

from multiprocessing import Pool,Manager
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    return_dict = {}
    start = time.time()
#    time.sleep(random.random() * 1)
    return_dict[name] = [33,44]
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))
    return return_dict

if __name__=='__main__':
    
    manager = Manager()
    return_dict = manager.dict()
    result = []
    print ('Parent process %s.' % os.getpid())
    p = Pool(processes = 4)
    for i in range(8):
        result.append(p.apply_async(long_time_task, args=(i,)))
    print ('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print ('All subprocesses done.')
    for r in result:
        print(r.get())

3. 多进程 + 多线程

import threading
import numpy as np
from multiprocessing import Pool

def pr(n, flist):
    print('pr')
    for i in flist:
        print('name {}, i {}'.format(n, i))


def process(n, flist):
    for i in flist:
        t = threading.Thread(target = pr, args = (n, np.arange(i, i+1)))
        t.start()
#         pr(n, np.arange(i, i + 1))

    
p = Pool()
print(os.getpid())
for name in ['a', 'b', 'c']:
    print('name',name)
    p.apply_async(process, args = (name, np.arange(1,5)))

p.close()
p.join()

结果:

27878
name a
name b
name prc
prprprpr
prprprpr

pr


name a, i 3pr
name a, i 4

name b, i 3name a, i 5name b, i 4name b, i 5
prname b, i 2




name a, i 2



name c, i 2
name c, i 5name c, i 3
name c, i 4

如何先多线程,再多进程的话,

%%time
import threading
import numpy as np
from multiprocessing import Pool

def pr(n, flist):
    print('pr')
    for i in flist:
        i += 1
        print('name {}, i {}'.format(n, i))


def process(n, flist):
    p = Pool()
    print(os.getpid())
    for i in flist:
        p.apply_async(pr, args = (n, np.arange(i, i+1)))
    p.close()
    p.join()

for name in ['a', 'b', 'c']:
    print('name',name)
    t = threading.Thread(target = process, args = (name, np.arange(1, 5)))
    t.start()

结果:

name a
name b
name c
CPU times: user 16 ms, sys: 2.59 s, total: 2.61 s
Wall time: 2.6 s
27878
prprprpr



name a, i 5name a, i 2name a, i 4name a, i 3




27878
prprpr

pr
name b, i 4name b, i 2name b, i 3



name b, i 5
27878
pr
pr
prprname c, i 2



name c, i 3name c, i 4name c, i 5

好像python的多线程是伪多线程,只是交替进行线程运行。

4. 嵌套多进程

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import multiprocessing
# We must import this explicitly, it is not imported by the top-level
# multiprocessing module.
import multiprocessing.pool
import time

from random import randint


class NoDaemonProcess(multiprocessing.Process):
    # make 'daemon' attribute always return False
    def _get_daemon(self):
        return False
    def _set_daemon(self, value):
        pass
    daemon = property(_get_daemon, _set_daemon)

# We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool
# because the latter is only a wrapper function, not a proper class.
class MyPool(multiprocessing.pool.Pool):
    Process = NoDaemonProcess

def sleepawhile(t):
    print("Sleeping %i seconds..." % t)
    time.sleep(t)
    return t

def work(num_procs):
    print("Creating %i (daemon) workers and jobs in child." % num_procs)
    pool = multiprocessing.Pool(num_procs)

    result = pool.map(sleepawhile,
        [randint(1, 5) for x in range(num_procs)])

    # The following is not really needed, since the (daemon) workers of the
    # child's pool are killed when the child is terminated, but it's good
    # practice to cleanup after ourselves anyway.
    pool.close()
    pool.join()
    return result

def test(n):
    print("run",n)
    print("Creating 5 (non-daemon) workers and jobs in main process.")
    pool = MyPool(5)

    result = pool.map(work, [randint(1, 5) for x in range(5)])

    pool.close()
    pool.join()
    print(result)

if __name__ == '__main__':
    test(1)
    print("run again.")
    test(2)