1.守护线程
#1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束, #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
import time from threading import Thread def func1(): while True: print(123) time.sleep(1) def func2(): print(456) time.sleep(5) t1 = Thread(target =func1) t1.daemon = True t1.start() t2 = Thread(target =func2) t2.start() print('***')
2.
from threading import Lock,Thread import time def func(lock): lock.acquire() global n temp = n time.sleep(0.2) n = temp -1 lock.release() n = 10 t_list = [] lock = Lock() for i in range(10): t = Thread(target = func,args = (lock,)) t.start() t_list.append(t) for i in t_list:i.join() print(n)
from threading import Thread,Lock import time noodle_lock = Lock() fork_lock = Lock() def eat1(name): noodle_lock.acquire() print('拿到面条%s'%name) fork_lock.acquire() print('拿到叉子%s'%name) print('%s吃面'%name) fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print('拿到叉子%s'%name) time.sleep(1) noodle_lock.acquire() fork_lock.acquire() print('拿到面条%s'%name) print('%s吃面'%name) fork_lock.release() noodle_lock.release() Thread(target = eat1,args =('shang',) ).start() Thread(target = eat2,args =('shan',) ).start() Thread(target = eat1,args =('sha',) ).start() Thread(target = eat2,args =('sh',) ).start()
from threading import Thread,RLock import time fork_lock = noodle_lock =RLock() def eat1(name): noodle_lock.acquire() print('拿到面条%s'%name) fork_lock.acquire() print('拿到叉子%s'%name) print('%s吃面'%name) fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print('拿到叉子%s'%name) time.sleep(1) noodle_lock.acquire() print('拿到面条%s'%name) print('%s吃面'%name) fork_lock.release() noodle_lock.release() Thread(target = eat1,args =('sha',) ).start() Thread(target = eat2,args =('sh',) ).start() Thread(target = eat1,args =('shang',) ).start() Thread(target = eat2,args =('shan',) ).start() # from threading import RLock # rlock = RLock() # rlock.acquire() # rlock.acquire() # rlock.acquire() # print(123) #
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
3.信号量
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
import time from threading import Semaphore,Thread def func(sem,a,b): sem.acquire() time.sleep(1) print(a+b) sem.release() sem = Semaphore(5) for i in range(10): t = Thread(target = func,args= (sem,i,i+5)) t.start()
4.事件
同进程的一样
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
# 事件被创建的时候 # False状态 # wait() 阻塞 # True状态 # wait() 非阻塞 # clear 设置状态为False # set 设置状态为True # 数据库 - 文件夹 # 文件夹里有好多excel表格 # 1.能够更方便的对数据进行增删改查 # 2.安全访问的机制 # 起两个线程 # 第一个线程 : 连接数据库 # 等待一个信号 告诉我我们之间的网络是通的 # 连接数据库 # 第二个线程 : 检测与数据库之间的网络是否连通 # time.sleep(0,2) 2 # 将事件的状态设置为True import time import random from threading import Thread,Event def connect_db(e): count = 0 while count < 3: e.wait(0.5) # 状态为False的时候,我只等待1s就结束 if e.is_set() == True: print('连接数据库') break else: count += 1 print('第%s次连接失败'%count) else: raise TimeoutError('数据库连接超时') def check_web(e): time.sleep(random.randint(0,3)) e.set() e = Event() t1 = Thread(target=connect_db,args=(e,)) t2 = Thread(target=check_web,args=(e,)) t1.start() t2.start()
5。条件
使得线程等待,只有满足某条件时,才释放n个线程
from threading import Condition,Thread def func(conn,i): conn.acquire() conn.wait() print('in 第%s个函数里'%i) conn.release() conn = Condition() for i in range(10): Thread(target = func,args = (conn,i)).start() while True: num = int(input('>>>>')) conn.acquire() conn.notify(num) conn.release()
6.定时器
定时器,指定n秒后执行某个操作
import time from threading import Timer def func(): print('时间同步') while True: Timer(2,func).start() time.sleep(2)
7.线程队列
queue队列 :使用import queue,用法与进程Queue一样
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(先进先出): first second third '''
import queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(后进先出): third second first '''
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c') '''
# queue import queue q = queue.Queue() # 队列 先进先出 # q.put() # q.get() # q.put_nowait() # q.get_nowait() # q = queue.LifoQueue() # 栈 先进后出 # q.put(1) # q.put(2) # q.put(3) # print(q.get()) # print(q.get()) q = queue.PriorityQueue() # 优先级队列 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) q.put((-5,'d')) q.put((1,'?')) print(q.get())
8.线程池
Python标准模块--concurrent.futures
#1 介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor: 进程池,提供异步调用 Both implement the same interface, which is defined by the abstract Executor class. #2 基本方法 #submit(fn, *args, **kwargs) 异步提交任务 #map(func, *iterables, timeout=None, chunksize=1) 取代for循环submit的操作 #shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作 wait=True,等待池内所有任务执行完毕回收完资源后才继续 wait=False,立即返回,并不会等待池内的任务执行完毕 但不管wait参数为何值,整个程序都会等到所有任务执行完毕 submit和map必须在shutdown之前 #result(timeout=None) 取得结果 #add_done_callback(fn) 回调函数
import time from concurrent.futures import ThreadPoolExecutor def func(n): time.sleep(2) print(n) return n*n def call_back(m): print('结果是 %s'%m.result()) tpool = ThreadPoolExecutor(max_workers=5) # 默认 不要超过cpu个数*5 for i in range(20): tpool.submit(func,i).add_done_callback(call_back) # tpool.map(func,range(20)) # 拿不到返回值 # t_lst = [] # for i in range(20): # t = tpool.submit(func,i) # t_lst.append(t) # tpool.shutdown() # close+join # # print('主线程') # for t in t_lst:print('***',t.result()) # ftp # 并发编程
9.协程
from greenlet import greenlet import gevent def eat(): print('eating start') g2.switch() print('eating ') g2.switch() def play(): print('playing start') g1.switch() print('playing ') g1 = greenlet(eat) g2 = greenlet(play) g1.switch()
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、
需要强调的是:
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点如下:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 #2. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点如下:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程 #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结协程特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制)
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。 g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
from gevent import monkey;monkey.patch_all() import time import gevent def eat(): print('eating start') time.sleep(1) print('eating end') def play(): print('playing start') time.sleep(1) print('playing end') g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() # 进程和线程的任务切换由操作系统完成 # 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果
我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程
from gevent import monkey;monkey.patch_all() import time import gevent import threading def eat(): print(threading.current_thread().getName()) print(threading.current_thread()) print('eating start') time.sleep(1) print('eating end') def play(): print(threading.current_thread().getName()) print(threading.current_thread()) print('playing start') time.sleep(1) print('playing end') g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join()
from gevent import monkey;monkey.patch_all() import time import gevent def task(): time.sleep(1) print(123) def sync(): for i in range(10): task() def asyn(): g_list = [] for i in range(20): g = gevent.spawn(task) g_list.append(g) gevent.joinall(g_list) # sync() asyn()
from gevent import spawn,joinall,monkey;monkey.patch_all() import time def task(pid): """ Some non-deterministic task """ time.sleep(0.5) print('Task %s done' % pid) def synchronous(): # 同步 for i in range(10): task(i) def asynchronous(): # 异步 g_l=[spawn(task,i) for i in range(10)] joinall(g_l) print('DONE') if __name__ == '__main__': print('Synchronous:') synchronous() print('Asynchronous:') asynchronous() # 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 # 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数, # 后者阻塞当前流程,并执行所有给定的greenlet任务。执行流程只会在 所有greenlet执行完后才会继续向下走。
from gevent import monkey;monkey.patch_all() import gevent import requests from urllib.request import urlopen def get_url(url): res1 = urlopen(url) content = res1.read().decode('utf-8') return len(content) g1 = gevent.spawn(get_url,'http://www.baidu.com') g2 = gevent.spawn(get_url,'http://www.sogou.com') # g3 = gevent.spawn(get_url,'http://www.souhu.com') g4 = gevent.spawn(get_url,'http://www.hao123.com') gevent.joinall([g1,g2,g4]) print(g1.value) print(g2.value) # print(g3.value) print(g4.value)
from gevent import monkey;monkey.patch_all() import gevent import socket def talk(conn): conn.send(b'hello') print(conn.recv(1024).decode('utf-8')) conn.close() sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while True: conn,addr = sk.accept() gevent.spawn(talk,conn) sk.close()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) print(sk.recv(1024)) msg = input('>>>>').encode('utf-8') sk.send(msg) sk.close()