一、threading模块

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。

1.开启线程的两种方式(同Process)

方法一

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadimport timedef sayhi(name):
    time.sleep(2)    print('%s say hello' %name)if __name__ == '__main__':
    t=Thread(target=sayhi,args=('hh',))
    t.start()    print('主线程')

iOS培训,Swift培训,苹果开发培训,移动开发培训

主线程
hh say hello

 

方法二

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadimport timeclass Sayhi(Thread):    def __init__(self,name):
        super().__init__()
        self.name=name    def run(self):
        time.sleep(2)        print('%s say hello' % self.name)if __name__ == '__main__':
    t = Sayhi('hh')
    t.start()    print('主线程')

iOS培训,Swift培训,苹果开发培训,移动开发培训

主线程
hh say hello

 

2.在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

  • 开启速度,主进程下开启线程速度较快。

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadfrom multiprocessing import Processimport osdef work():    print('hello')if __name__ == '__main__':    #在主进程下开启线程
    t=Thread(target=work)
    t.start()    print('主进程-->线程')    '''
    打印结果:
    hello
    主进程-->线程    '''

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()    print('主进程-->子进程')    '''
    打印结果:
    主进程-->子进程
    hello    '''

iOS培训,Swift培训,苹果开发培训,移动开发培训

hello
主进程-->线程
主进程-->子进程
hello

 

  • 开启PID

在主进程下开启多个线程,每个线程都跟主进程的pid一样,开多个进程,每个进程都有不同的pid

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadfrom multiprocessing import Processimport osdef work():    print('hello',os.getpid())if __name__ == '__main__':    #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()    print('主进程-->线程pid',os.getpid())    #part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()    print('主进程-->子进程pid',os.getpid())

iOS培训,Swift培训,苹果开发培训,移动开发培训

hello 13002hello 13002主进程-->线程pid 13002主进程-->子进程pid 13002hello 13003hello 13004

 

iOS培训,Swift培训,苹果开发培训,移动开发培训 多线程并发的socket服务端

iOS培训,Swift培训,苹果开发培训,移动开发培训 客户端

 

三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Thread
msg_l=[]
format_l=[]def talk():    while True:
        msg=input('>>: ').strip()        if not msg:continue
        msg_l.append(msg)def format_msg():    while True:        if msg_l:
            res=msg_l.pop()
            format_l.append(res.upper())def save():    while True:        if format_l:
            with open('db.txt','a',encoding='utf-8') as f:
                res=format_l.pop()
                f.write('%s\n' %res)if __name__ == '__main__':
    t1=Thread(target=talk)
    t2=Thread(target=format_msg)
    t3=Thread(target=save)
    t1.start()
    t2.start()
    t3.start()

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

3.线程的join与setdaemon

与进程的方法都是类似的,其实是multiprocessing模仿threading的接口

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadimport timedef sayhi(name):
    time.sleep(2)    print('%s say hello' %name)if __name__ == '__main__':
    t=Thread(target=sayhi,args=('h',))
    t.setDaemon(True)
    t.start()
    t.join()    print('主线程')    print(t.is_alive())

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

4.线程相关的其他方法补充

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 

 

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Threadimport threadingfrom multiprocessing import Processimport osdef work():    import time
    time.sleep(3)    print(threading.current_thread().getName())if __name__ == '__main__':    #在主进程下开启线程
    t=Thread(target=work)
    t.start()    print(threading.current_thread().getName())    print(threading.current_thread()) #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程
    print(threading.active_count())    print('主线程/主进程')

iOS培训,Swift培训,苹果开发培训,移动开发培训

MainThread<_MainThread(MainThread, started 140736678523840)>[<_MainThread(MainThread, started 140736678523840)>, <Thread(Thread-1, started 123145314349056)>]2主线程/主进程
Thread-1

 

二、 Python GIL(Global Interpreter Lock)

全局解释器锁。在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

 

补充:

1. cpu到底是用来做计算的,还是用来做I/O的?

1. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能

2. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处

一个工人相当于cpu,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到等待原材料的到来。

如果你的工厂干的大多数任务都要有准备原材料的过程(I/O密集型),那么你有再多的工人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活,

反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高

结论:

  对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用

 

我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

方案一:开启四个进程

方案二:一个进程下,开启四个线程

 

单核情况下,分析结果: 

  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜

  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

 

多核情况下,分析结果:

  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜

  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

 

结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

 

iOS培训,Swift培训,苹果开发培训,移动开发培训

#计算密集型from threading import Threadfrom multiprocessing import Processimport osimport timedef work():
    res=0    for i in range(1000000):
        res+=iif __name__ == '__main__':
    t_l=[]
    start_time=time.time()    # for i in range(300): #串行
    #     work()

    for i in range(300):        # t=Thread(target=work) #多线程49.64094281196594
        t=Process(target=work) #多进程11.664679050445557        t_l.append(t)
        t.start()    for i in t_l:
        i.join()

    stop_time=time.time()    print('run time is %s' %(stop_time-start_time))    print('主线程')# run time is 49.64094281196594# 主线程# run time is 11.664679050445557# 主线程

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

iOS培训,Swift培训,苹果开发培训,移动开发培训

#I/O密集型from threading import Threadfrom multiprocessing import Processimport timeimport osdef work():
    time.sleep(2) #模拟I/O操作,可以打开一个文件来测试I/O,与sleep是一个效果
    print(os.getpid())if __name__ == '__main__':
    t_l=[]
    start_time=time.time()    for i in range(1000):
        t=Thread(target=work) #耗时大概为2秒
        # t=Process(target=work) #耗时大概为25秒,创建进程的开销远高于线程,而且对于I/O密集型,多cpu根本不管用        t_l.append(t)
        t.start()    for t in t_l:
        t.join()
    stop_time=time.time()    print('run time is %s' %(stop_time-start_time))

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

结论:

多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析

 

三、同步锁

iOS培训,Swift培训,苹果开发培训,移动开发培训

import timeimport threadingdef addNum():    global num #在每个线程中都获取这个全局变量
    #num-=1
    temp=num
    time.sleep(0.1)
    num =temp-1  # 对此公共变量进行-1操作num = 100  #设定一个共享变量thread_list = []for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)for t in thread_list: #等待所有线程执行完毕    t.join()print('Result: ', num)

iOS培训,Swift培训,苹果开发培训,移动开发培训

Result:  99

 

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

iOS培训,Swift培训,苹果开发培训,移动开发培训

import threading

R=threading.Lock()

R.acquire()'''对公共数据的操作'''R.release()

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

补充:

GIL VS Lock

    Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 

 首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

    然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

 最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock   

 

四、死锁与递归锁

死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

iOS培训,Swift培训,苹果开发培训,移动开发培训

from threading import Thread,Lockimport time
mutexA=Lock()
mutexB=Lock()class MyThread(Thread):    def run(self):
        self.func1()
        self.func2()    def func1(self):
        mutexA.acquire()        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()    def func2(self):
        mutexB.acquire()        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()if __name__ == '__main__':    for i in range(10):
        t=MyThread()
        t.start()'''Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了'''

iOS培训,Swift培训,苹果开发培训,移动开发培训

 

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

 mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

 

五、信号量Semahpore

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

iOS培训,Swift培训,苹果开发培训,移动开发培训

import threadingimport time

semaphore = threading.Semaphore(5)def func():    if semaphore.acquire():        print (threading.currentThread().getName() + ' get semaphore')
        time.sleep(2)
        semaphore.release()for i in range(20):
  t1 = threading.Thread(target=func)
  t1.start()

iOS培训,Swift培训,苹果开发培训,移动开发培训

与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

 

六、Event

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

iOS培训,Swift培训,苹果开发培训,移动开发培训

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

iOS培训,Swift培训,苹果开发培训,移动开发培训

可以考虑一种应用场景(仅仅作为说明),例如,我们有多个线程从Redis队列中读取数据来处理,这些线程都要尝试去连接Redis的服务,一般情况下,如果Redis连接不成功,在各个线程的代码中,都会去尝试重新连接。如果我们想要在启动时确保Redis服务正常,才让那些工作线程去连接Redis服务器,那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作:主线程中会去尝试连接Redis服务,如果正常的话,触发事件,各工作线程会尝试连接Redis服务。

iOS培训,Swift培训,苹果开发培训,移动开发培训

import threadingimport timeimport logging

logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s',)def worker(event):
    logging.debug('Waiting for redis ready...')
    event.wait()

http://www.cnblogs.com/smallmars/p/7149507.html