第十 线程与锁

一、线程与进程区别

  1. Threads share the address space of the process that created it; processes have their own address space.
  2. Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
  3. Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
  4. New threads are easily created; new processes require duplication of the parent process.
  5. Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
  6. Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

二、开启线程两种方式

方式一:

from threading import Thread
import os

def work(n):
    print('%s is running' %n)

if __name__ == '__main__':
    t=Thread(target=work,args=('ckl',))
    t.start()
    print("---main---")

运行结果:

方式二:

from threading import Thread
import os

class CklThread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print('%s is running' %self.name)

if __name__ == '__main__':
    t=CklThread('ckl')
    t.start()
    print("---main---")

运行结果:

 线程与进程pid

线程pid来自于主线程(所有线程来自同一pid):

from threading import Thread
import os

def work():
    print('%s is running' %os.getpid())

if __name__ == '__main__':
    t1=Thread(target=work,)
    t2=Thread(target=work,)
    t1.start()
    t2.start()
    print("---main is %s ---" %os.getpid())

运行结果:

进程pid

进程产生不同的pid

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('%s is running' %os.getpid())

if __name__ == '__main__':
    p1=Process(target=work,)
    p2=Process(target=work,)
    p1.start()
    p2.start()
    print("---main is %s ---" %os.getpid())

运行结果:

多线程共享同一进程资源

多进程:

from threading import Thread
from multiprocessing import Process
import os

n=99
def work():
    global n
    n = 1

if __name__ == '__main__':
    p1=Process(target=work,)
    p1.start()
    print("---main is %s ---" %n)

运行结果:

#分析:进程之间不共享资源,主进程开启,子进程也开启,子进程修改了n,但是没有共享给主进程,主进程看到的还是99

多线程

from threading import Thread
from multiprocessing import Process
import os

n=99
def work():
    global n
    n = 1

if __name__ == '__main__':
    t1=Thread(target=work,args=())
    t1.start()
    print("---main is %s ---" %n)

运行结果:

#分析:多线程共享同一个进程内的资源,主进程启动,线程也启动,线程修改了n,所以主进程看到的也是修改后的结果。

多线程共享内存练习:

from threading import Thread
msg_list = []
format_list = []

#输入内容
def say():
    while True:
        msg = input('>>: ').strip()
        msg_list.append(msg)

#格式化
def format():
    while True:
        if msg_list:
            data = msg_list.pop()
            format_list.append(data.upper())

#存储内容
def save():
    while True:
        if format_list:
            data = format_list.pop()
            with open('db.txt','a') as fa:
                fa.write('%s\n' %data)


if __name__ == '__main__':
    t1 = Thread(target=say)
    t2 = Thread(target=format)
    t3 = Thread(target=save)
    t1.start()
    t2.start()
    t3.start()

运行:

查看文件:

三、Thread对象属性与方法

一些了解方法

from threading import Thread,active_count,enumerate
import os

def work():
    print('%s is running' %os.getpid())

if __name__ == '__main__':
    t1=Thread(target=work,)
    t1.start()
    # print(t1.is_alive()) #有时候True,有时候False,取决于操作系统回收时间
    # print(t1.getName()) #Thread-1


    print("---main ---")
    print(active_count()) #2当前活着的进程数,也取决操作系统,不固定
    print(enumerate()) #当前活跃的进程对象,返回列表,当前主线程和子线程
    # [ < _MainThread(MainThread, started 140735301873664) >, < Thread(Thread - 1, started 123145307557888) >]

current_thread 获取当前线程对象

示例一:

from threading import Thread,active_count,enumerate,current_thread
import os

def work():
    print('%s is running' %current_thread().getName()) #Thread-1 is running

if __name__ == '__main__':
    t1=Thread(target=work,)
    t1.start()

    print("---main ---")

示例二:

from threading import Thread,active_count,enumerate,current_thread
from multiprocessing import Process
import time

print(current_thread())

运行结果:

 #没有调用,为何有一个主线程?因为运行的时候,就会产生一个进程的(主线程)。

如下示例进一步理解:

from threading import Thread,active_count,enumerate,current_thread
from multiprocessing import Process
import time

def work():
    print('%s is running' %current_thread().getName()) #MainThrad 这是子进程的

if __name__ == '__main__':
    p=Process(target=work())
    p.start()
    print(current_thread()) #MainThrad 这是主进程的

运行结果:

#第一个是子进程的MainThread,第二个是主进程的MainThread,主线程在执行层面代表了其所在进程的执行过程。

from threading import Thread,active_count,enumerate,current_thread
from multiprocessing import Process
import time

def work():
    print('%s is running' %current_thread().getName()) 

if __name__ == '__main__':
    t1 =Thread(target=work())
    t2 = Thread(target=work())
    t3 = Thread(target=work())
    t1.start()
    t2.start()
    t3.start()
    print(current_thread())

运行结果:

#运行过程:一个进程(主线程)其余都是子进程。

 四、守护线程

守护进程运行示例

from multiprocessing import Process
import time

def work1():
    print('w1 is running')
    time.sleep(1)
    print('w1 done')

def work2():
    print('w2 is running')
    time.sleep(3)
    print('w2 done')

if __name__ == '__main__':
    p1 = Process(target=work1)
    p2 = Process(target=work2)
    p1.daemon = True #守护进程,主进程结束,就跟着结束
    p1.start()
    p2.start()
    print('-- main --')

运行结果:

#分析:1.主进程启动,运行两个子进程。2.启动p1和p2,但是p1是守护进程,也就是主进程结束,p1也就结束。3.主进程是代码运行完毕就结束了。4.所以p1还没启动就跟着

结束了。5.主进程虽然结束,但是必须等着子进程运行完毕才结束,所以p2运行完毕(防止僵尸进程)。

 守护线程

主线程一定要等待所有非守护线程运行完毕,才结束

from threading import Thread
import time

def work1():
    print('w1 is running')
    time.sleep(1)
    print('w1 done')



if __name__ == '__main__':
    t1 = Thread(target=work1)
    t1.daemon = True #守护进程,主进程结束,就跟着结束
    t1.start()
    print('-- main --')

运行结果:

 #分析运行:1.运行主线程。2.运行子线程打印运行。3.主线程运行完毕,这时候运行子线程,发现子线程是守护线程,所以也跟着结束掉。所以没有打印运行结束。

进一步示例:

from threading import Thread
import time

def work1():
    print('w1 is running')
    time.sleep(1)
    print('w1 done')

def work2():
    print('w2 is running')
    time.sleep(3)
    print('w2 done')

if __name__ == '__main__':
    t1 = Thread(target=work1)
    t2 = Thread(target=work2)
    t1.daemon = True
    t1.start()
    t2.start()
    print('-- main --')

运行结果:

#运行分析:1.运行主线程。2.运行t1和t2,打印开始运行。3.这时候主线程运行完毕,t1是守护进程,应该也会跟着结束。但主线程要等待其它非守护线程运行

完毕才结束,所以等待t2运行完毕。4.等待t2的时间足够长,这时候t1也已经运行完毕,打印t1运行结束,等一会t2也运行结束。

 五、GIL

GIL说明:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple 
native threads from executing Python bytecodes at once. This lock is necessary mainly 
because CPython’s memory management is not thread-safe. (However, since the GIL 
exists, other features have grown to depend on the guarantees that it enforces.)

GIL阻止同时执行多个python线程,这个锁是必须滴,因为Cpython的内存管理部安全。其它特效也依赖GIL增强安全。

GIL锁是加载解释器上的,保证代码执行不会被python内存回收给回收掉,只能保证解释器层面,不能保证代码层面。

进程可以利用多核,开销大。线程无法利用多核,开销小。

线程的互斥锁

多线程计算示例:

from threading import Thread
import time

n=100
def work():
    global n
    temp = n
    time.sleep(1)
    n = temp -1


if __name__ == '__main__':
    l = []
    for i in range(100):
        t = Thread(target=work)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print(n)

结果:

#运行分析:1.定义个全局n=100。2.定义一个函数,对全局n-1。3.开启100个线程,执行函数,并且等待所有线程执行完毕,也就是100减去100次,等于0。为何是99?

#结果说明:是因为多线程在执行函数减1的时候,遇到阻塞,然后就把执行权限让出,第二个也拿到执行后,也阻塞,初始值始终是100,最后阻塞了100次,执行了100次,每次都是100-1,所以结果就是99.如何避免这个问题呢?就是线程锁

from threading import Thread,Lock
import time

n=100
def work():
    global n
    mutex.acquire() #加锁
    temp = n
    time.sleep(0.1)
    n = temp - 1
    mutex.release() #释放锁


if __name__ == '__main__':
    mutex = Lock() #定义锁
    l = []
    for i in range(100):
        t = Thread(target=work)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print(n)

运行结果:

#运行结果正确,但是这样一来,就把并行变成了串行,那还有什么意义?那是不是不用互斥锁,直接使用join,这样写也可以?

from threading import Thread
import time

n=100
def work():
    global n
    temp = n
    time.sleep(0.1)
    n = temp - 1


if __name__ == '__main__':
    for i in range(100):
        t = Thread(target=work)
        t.start()
        t.join()

    print(n)

运行结果:

运行结果正确,那么互斥锁岂不是没有用?其实不是的,join是将每次运行都等待,所有代码从上之下而执行,完全串行。而互斥锁,值将加锁的值进行串行,这里刚好是第一行,效果一样,如果还有其他操作呢?加上时间看看效果?

没有互斥锁:

from threading import Thread
import time

n=100
start_time = time.time()
def work():
    global n
    time.sleep(0.05) #模拟其它操作
    temp = n
    time.sleep(0.1)
    n = temp - 1


if __name__ == '__main__':
    for i in range(100):
        t = Thread(target=work)
        t.start()
        t.join()

    print("cost the time is %s and value is %s"%(time.time()-start_time,n))

运行结果:

有互斥锁:

from threading import Thread,Lock
import time

n=100
start_time = time.time()
def work():
    time.sleep(0.05) #模拟其它操作
    global n
    mutex.acquire() #加锁
    temp = n
    time.sleep(0.1)
    n = temp - 1
    mutex.release() #释放锁


if __name__ == '__main__':
    mutex = Lock() #定义锁
    l = []
    for i in range(100):
        t = Thread(target=work)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print("cost the time is %s and value is %s" % (time.time() - start_time, n))

运行结果:

#这是因为,互斥锁串行的是加锁的部分,其余代码依然是并行执行。

 多进程与多线程时机

 多进程可以利用多核,但是开销大。多线程不可以利用多核,但是开销小。

计算密集型示例(多进程):

from multiprocessing import Process
import time

def work():
    res = 1
    for i in range(10000000):
        res += 1

if __name__ == '__main__':
    p_l = []
    start_time = time.time()
    for i in range(4):
        p=Process(target=work)
        p_l.append(p)
        p.start()

    for p in p_l:
        p.join()

    print(time.time()-start_time)

运行结果:

 如果是多线程:

from multiprocessing import Process
from threading import Thread
import time

def work():
    res = 1
    for i in range(10000000):
        res += 1

if __name__ == '__main__':
    p_l = []
    start_time = time.time()
    for i in range(4):
        p=Thread(target=work)
        p_l.append(p)
        p.start()

    for p in p_l:
        p.join()

    print(time.time()-start_time)

运行结果:

 模拟IO密集型(多进程):

from multiprocessing import Process
from threading import Thread
import time

def work():
    time.sleep(3)

if __name__ == '__main__':
    p_l = []
    start_time = time.time()
    for i in range(4):
        p=Process(target=work)
        p_l.append(p)
        p.start()

    for p in p_l:
        p.join()

    print(time.time()-start_time)

运行结果:

IO密集型(多线程):

from multiprocessing import Process
from threading import Thread
import time

def work():
    time.sleep(3)

if __name__ == '__main__':
    p_l = []
    start_time = time.time()
    for i in range(4):
        # p=Process(target=work)
        p=Thread(target=work)
        p_l.append(p)
        p.start()

    for p in p_l:
        p.join()

    print(time.time()-start_time)

运行结果:

#总结:计算密集型的应用,使用多进程,利用多核的优势,加快计算速度。IO密集型的应用,使用多线程,减少切换及进程频繁创建操作,可以提升速度。

 死锁与递归锁

死锁示例:

from threading import Thread,Lock
import time

mutexA=Lock()
mutexB=Lock()

class CklThread(Thread):
    def run(self):
        self.f1()
        self.f2()


    def f1(self):
        mutexA.acquire()
        print("\033[1;33m %s obtain lock A\033[0m" %self.name)
        mutexB.acquire()
        print("\033[1;34m %s obtain lock B\033[0m" %self.name)
        mutexB.release()
        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print("\033[1;35m %s obtain lock B\033[0m" %self.name)
        time.sleep(1)
        mutexA.acquire()
        print("\033[1;36m %s obtain lock A\033[0m" %self.name)
        mutexA.release()
        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t = CklThread()
        t.start()

运行结果:

 #运行分析:1.运行f1,线程1,获得A锁。2.线程1紧接着获得B锁,3.线程1释放B锁。4.线程1释放A锁。5.线程1得到B锁,等待得到B锁,这时候,休息1s,这期间,线程2得到A锁,准备获得B锁,发现没有,等待线程1释放。线程1睡好,准备获得A锁,发现没有,所以等待线程2释放。就这样。。。死锁了

解决办法:递归锁

from threading import Thread,Lock,RLock
import time

mutex=RLock()

class CklThread(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutex.acquire()
        print("\033[1;33m %s obtain lock A\033[0m" %self.name)
        mutex.acquire()
        print("\033[1;34m %s obtain lock B\033[0m" %self.name)
        mutex.release()
        mutex.release()

    def f2(self):
        mutex.acquire()
        print("\033[1;35m %s obtain lock B\033[0m" %self.name)
        time.sleep(1)
        mutex.acquire()
        print("\033[1;36m %s obtain lock A\033[0m" %self.name)
        mutex.release()
        mutex.release()

if __name__ == '__main__':
    for i in range(10):
        t = CklThread()
        t.start()

运行结果:

#递归锁原理是内部有一个计数器,这个计数器为0,就可以获得锁,如果不为0,这期间只能有一个线程获得锁。所以上面的🌰,线程1没有释放B锁,线程2是无法获得A锁的。

 六、semaphore

semaphore相当于互斥锁一样来工作,来看示例:

from threading import Thread,current_thread,Semaphore
import time,random

sm = Semaphore(3) #同时可执行的任务
def work():
    sm.acquire()
    print("%s is eating noodles" %current_thread().getName())
    time.sleep(random.randint(1,5))
    sm.release()


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=work)
        t.start()

运行结果:

 #期间按下回车,一次性只有3个线程在运行。这个跟进程池有什么区别?

进程池是只有3个进程在执行,一共就3个。信号量是一次性造了10个线程,但同时只运行3个.

七、 Event

 如果同时运行多个线程,第二个线程要等第一个线程的返回状态来运行下一步操作,那么线程之间的这种状态传递如何实现呢?

这就需要用到Event了,Event内部有一个标志位,如果标志位为True则正常,如果为False则为异常。下面模拟一个连接mysql的示例:

from threading import Thread,current_thread,Event
import time
ev = Event()

def conn_mysql():
    print("%s is ready to connect mysql" %current_thread().getName())
    ev.wait() #等待返回结果
    print("%s has connected" %current_thread().getName())

def check_mysql():
    print("Begin to check mysql status...")
    time.sleep(3)
    ev.set() #设置状态为True

if __name__ == '__main__':
    t1 = Thread(target=conn_mysql)
    t2 = Thread(target=conn_mysql)
    t1.start()
    t2.start()
    check_mysql()

运行结果:

#等待3秒后,检车event返回为ture,进行连接。那么还可以设置超时时间,判断当前状态等:

运行示例:

from threading import Thread,current_thread,Event
import time
ev = Event()

def conn_mysql():
    count=1
    ev.clear() #设置初始值为False
    while not ev.isSet():
        if count > 3:
            raise ConnectionError("Connect mysql error")
        print("%s is in the %s times ready to connect mysql" %(current_thread().getName(),count,))
        ev.wait(0.5) #阻塞线程,等待超时为0.5
        count += 1
    print("%s has connected" %current_thread().getName())

def check_mysql():
    print("Begin to check mysql status...")
    time.sleep(3)
    ev.set() #设置状态为True

if __name__ == '__main__':
    t1 = Thread(target=conn_mysql)
    t2 = Thread(target=conn_mysql)
    t1.start()
    t2.start()
    check_mysql()

运行结果:

八、线程queue

先进先出:

import queue

q = queue.Queue(3) #队列,先进先出
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

结果:

 后进先出:

import queue

q = queue.LifoQueue(3) #堆栈,先进后出
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

运行结果:

优先级

import queue

q = queue.PriorityQueue(3) #数字越小,优先级越高
q.put((10,'data1'))
q.put((12,'data2'))
q.put((8,'data3'))

print(q.get())
print(q.get())
print(q.get())

运行结果:

 Timer

#5s后,让老习给我汇报

from threading import Timer

def work(n):
    print("I had %s report to me" %n)


t = Timer(5,work,args=('laoxi',))
t.start()

结果:

 九、进程池与线程池

 concurrent.futures 为异步执行调用提供了一个更高级别的接口。

说明:https://docs.python.org/3/library/concurrent.futures.html

进程池:

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random

def work(n):
    print("%s is running" %os.getpid())
    time.sleep(random.randint(1,3))
    return n*2

if __name__ == '__main__':
    p=ProcessPoolExecutor() #不指定,默认是CPU核数
    objs_list = []
    for i in range(10):
        objs = p.submit(work,i) #相当于之前进程池的apply_async()
        objs_list.append(objs)

    p.shutdown() #相当于之前进程池的close()和join()
    for j in objs_list:
        print(j.result()) #相当于之前进程池的get()

运行结果:

 #同时有四个进程在执行,CPU的四个核数。

线程池

线程池的实现同进程池一样,就名字换一下。 

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os,time,random

def work(n):
    print("%s is running" %current_thread().getName())
    time.sleep(random.randint(1,3))
    return n*2

if __name__ == '__main__':
    p=ThreadPoolExecutor() #不指定,默认是CPU核数*5
    objs_list = []
    for i in range(22):
        objs = p.submit(work,i) #相当于之前进程池的apply_async()
        objs_list.append(objs)

    p.shutdown() #相当于之前进程池的close()和join()
    for j in objs_list:
        print(j.result()) #相当于之前进程池的get()

运行结果:

#运行结果说明:同时运行20个线程CPU核数*5,其它都一样。

用concurrent来实现回调函数

#之前回调函数

from multiprocessing import Pool
import requests
import json
import os

#下载链接
def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

#分析内容
def pasrse_page(res):
    print('<%s> parse:%s' %(os.getpid(),len(res)))


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.sohu.com',
        'https://www.163.com',
        'https://www.qq.com',
        'http://www.sina.com.cn',
        'http://www.jd.com'
    ]

    p=Pool(3) #进程池为3
    for url in urls:
        p.apply_async(get_page,args=(url,),callback=pasrse_page) #一旦某个获取网页结束,则主进程分析网页

    p.close()
    p.join()

运行结果:

 #同时有3个进程在执行。如果是使用concurrent呢?

from multiprocessing import Pool
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

import requests
import json
import os

#下载链接
def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

#分析内容
def pasrse_page(res):
    print('<%s> parse:%s' %(os.getpid(),len(res)))


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.sohu.com',
        'https://www.163.com',
        'https://www.qq.com',
        'http://www.sina.com.cn',
        'http://www.jd.com'
    ]

    # p=Pool(3) #进程池为3
    p=ProcessPoolExecutor()
    for url in urls:
        # p.apply_async(get_page,args=(url,),callback=pasrse_page) #一旦某个获取网页结束,则分析网页
        p.submit(get_page,url).add_done_callback(pasrse_page) #这个函数相当于回调函数
    # p.close()
    # p.join()
    p.shutdown()

运行结果:

#分析原因:

        p.apply_async(get_page,args=(url,),callback=pasrse_page) #这个回调函数拿到的是一个结果
        p.submit(get_page,url).add_done_callback(pasrse_page) #这个回调函数拿到的是一个对象,而非结果

修改如下:

from multiprocessing import Pool
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

import requests
import json
import os

#下载链接
def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

#分析内容
def pasrse_page(res):
    res = res.result() #得到结果
    print('<%s> parse:%s' %(os.getpid(),len(res)))


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.sohu.com',
        'https://www.163.com',
        'https://www.qq.com',
        'http://www.sina.com.cn',
        'http://www.jd.com'
    ]

    # p=Pool(3) #进程池为3
    p=ProcessPoolExecutor()
    for url in urls:
        # p.apply_async(get_page,args=(url,),callback=pasrse_page) #一旦某个获取网页结束,则分析网页
        p.submit(get_page,url).add_done_callback(pasrse_page) #这个函数相当于回调函数
    # p.close()
    # p.join()
    p.shutdown()

运行结果:

#并发四个进程在执行,主进程在分析结果

 线程池模拟网页分析:

from multiprocessing import Pool
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import requests
import json
import os

#下载链接
def get_page(url):
    print('<进程%s> get %s' %(current_thread().getName(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

#分析内容
def pasrse_page(res):
    res = res.result() #得到结果
    print('<%s> parse:%s' %(current_thread().getName(),len(res)))


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.sohu.com',
        'https://www.163.com',
        'https://www.qq.com',
        'http://www.sina.com.cn',
        'http://www.jd.com'
    ]

    # p=Pool(3) #进程池为3
    p=ThreadPoolExecutor(3)
    for url in urls:
        # p.apply_async(get_page,args=(url,),callback=pasrse_page) #一旦某个获取网页结束,则分析网页
        p.submit(get_page,url).add_done_callback(pasrse_page) #这个函数相当于回调函数
    # p.close()
    # p.join()
    p.shutdown()

运行结果:

#每次运行3个,因为指定了线程数。分析不再是主线程分析,而是哪个线程结束,哪个线程去分析。

concurrent的map的使用

 python的map方法回忆:

之前的进程池改用map方法实现:

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os,time,random

def work(n):
    print("%s is running" %os.getpid())
    time.sleep(random.randint(1,3))
    return n*2

if __name__ == '__main__':
    p=ProcessPoolExecutor()
    res = p.map(work,range(10)) #将每一次结果,当做参数传递给work
    p.shutdown()
    print(list(res)) #得到迭代器的结果

运行结果:

 十、协程

实现函数之间的切换,如何实现?之前的yield:

import time
def consumer():
    while True:
        res = yield

def producer():
    g = consumer()
    next(g)
    for i in range(10000000):
        g.send(i)

start = time.time()
producer()
print(time.time()-start)

运行结果:

这样切换没有意义,单纯切换,没有因为阻塞而切换,有更好的切换,greenlet模块,如下:

from greenlet import greenlet

def eat(name):
    print("%s eat 1" %name)
    g2.switch('ckl') #第一次switch传参
    print("%s eat 2" %name)
    g2.switch()

def play(name):
    print("%s play 1" %name)
    g1.switch()
    print("%s play 2" %name)

g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('ckl') #在第一次switch的时候传参

运行结果:

#这样实现了yield,但是仍然是没有意义的切换,没有因为阻塞切换,如果遇到阻塞才切换呢?这就使用另外一个模块,gevent

import gevent

def eat(name):
    print("%s eat 1" %name)
    gevent.sleep(3)
    print("%s eat 2" %name)


def play(name):
    print("%s play 1" %name)
    gevent.sleep(2)
    print("%s play 2" %name)

g1=gevent.spawn(eat,'ckl')
g2=gevent.spawn(play,'ckl')

gevent.joinall([g1,g2])

运行结果:

#运行分析:1.运行eat,打印ckl eat 1,这时候遇到阻塞。2.且到play,打印ckl paly 1,这时候遇到阻塞,切到eat,eat仍然为阻塞,切换到play,paly也是阻塞,这样反复切,最后play先阻塞完毕,所以打印ckl play 2,paly运行完毕,再切换到eat,打印ckl eat 2,这没有使用系统的time模块,默认是不识别系统的time,如果要识别,必须如下实现:

from gevent import monkey;monkey.patch_all()
import gevent
import time

def eat(name):
    print("%s eat 1" %name)
    time.sleep(3)
    print("%s eat 2" %name)


def play(name):
    print("%s play 1" %name)
    time.sleep(2)
    print("%s play 2" %name)

g1=gevent.spawn(eat,'ckl')
g2=gevent.spawn(play,'ckl')

gevent.joinall([g1,g2])

运行结果:

客户端和服务端并发的实现,使用gevent,如下:

服务端:

from gevent import monkey;monkey.patch_all()
from socket import *
import gevent

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)

def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    server('127.0.0.1',8080)

客户端:

from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
    c.connect((server_ip,port))

    count=0
    while True:
        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
        msg=c.recv(1024)
        print(msg.decode('utf-8'))
        count+=1
if __name__ == '__main__':
    for i in range(500):
        t=Thread(target=client,args=('127.0.0.1',8080))
        t.start()

运行结果服务端:

客户端:

#这样就可以在一个线程内,实现多并发的效果,这样就是协程。协程就是用户态下的轻量级线程。

 协程有点:

1.开销小,更加轻量级。

2.单个线程内实现并发,最大限度使用CPU

缺点:

1.协程本质是单线程,无法利用多核,可以一个程序开旭多个进程,每个进程多个线程,多个线程开启协程

2.协程出现阻塞,将阻塞整个线程 

posted @ 2017-06-26 11:12  ckl893  阅读(198)  评论(0)    收藏  举报