python------线程
一、为什么有了进程还要有线程
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其它资源,可以提高计算机的利用率。但进程还存在很多缺陷的,主要体现在:
1:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2:进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
线程的出现就是为了解决进程的缺陷。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
每一个进程中至少有一个线程。
二、线程和进程的区别
1:地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2:通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3:调度和切换:线程上下文切换比进程上下文切换要快得多。
4:在多线程操作系统中,进程不是一个可执行的实体。
三、线程的特点
1:轻型实体
2:独立调度和分派的基本单位
3:共享进程资源
4:可并发执行
四、用户级线程和内核级线程池
用户级线程和内核级线程池的区别:
1:内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
2:用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
3:用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
4:在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
5:用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
用户级线程的优缺点:
优点:
1:线程的调度不需要内核直接参与,控制简单。
2:可以在不支持线程的操作系统中实现。
3:创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
4:允许每个进程定制自己的调度算法,线程管理比较灵活。
5:线程能够利用的表空间和堆栈空间比内核级线程多。
6:同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。
缺点:资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
内核线程的优缺点:
优点:当有多个处理机时,一个进程的多个线程可以同时执行。
缺点:由内核进行调度。
五、threading模块
线程的创建
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('hjm',)) t.start() print('主线程')
from threading import Thread import time class 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('hjm') t.start() print('主线程')
多线程与多进程
from threading import Thread from multiprocessing import Process import os def 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())
from threading import Thread from multiprocessing import Process import os def work(): print('hello') if __name__ == '__main__': #在主进程下开启线程 t=Thread(target=work) t.start() print('主线程/主进程') ''' 打印结果: hello 主线程/主进程 ''' #在主进程下开启子进程 t=Process(target=work) t.start() print('主线程/主进程') ''' 打印结果: 主线程/主进程 hello '''
Thread实例对象方法:
isAlive(): 返回线程是否活动的
getName(): 返回线程名
setName(): 设置线程名
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量
threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate()) 有相同的结果
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('hjm',)) t.start() t.join() print('主线程') print(t.is_alive()) ''' hjm say hello 主线程 False '''
六、守护线程
进程和线程都应该遵循:守护XX会等待主XX运行完毕后被销毁。
运行完毕并非终止运行。
1:对主进程来说,运行完毕指的是主进程代码运行完毕。
2:对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程全部运行完毕,主线程才算运行完毕。
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('hjm',)) t.setDaemon(True) #必须在t.start()之前设置 t.start() print('主线程') print(t.is_alive()) ''' 主线程 True '''
from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------") ''' 123 456 main------- end123 end456 '''
七、锁
同步锁
from threading import Thread import os,time def work(): global n temp=n time.sleep(0.1) n=temp-1 if __name__ == '__main__': n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #结果可能为99
from threading import Thread,Lock import os,time def work(): global n lock.acquire() temp=n time.sleep(0.1) n=temp-1 lock.release() if __name__ == '__main__': lock=Lock() n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
#不加锁:并发执行,速度快,数据不安全 from threading import current_thread,Thread,Lock import os,time def task(): global n print('%s is running' %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:0.5154428482055664 n:99 '''
死锁和递归锁
进程和线程都有死锁和递归锁
死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
from threading import Lock from threading import Thread import time kz = Lock() #筷子锁 m = Lock() #面锁 def eat(name): kz.acquire() print('%s拿到筷子了'%name) m.acquire() print('%s拿到面了'%name) print('%s吃面'%name) m.release() kz.release() def eat2(name): m.acquire() print('%s拿到面了' % name) time.sleep(1) kz.acquire() print('%s拿到筷子了' % name) print('%s吃面' % name) kz.release() m.release() Thread(target=eat,args=('张三',)).start() Thread(target=eat2,args=('李四',)).start() Thread(target=eat,args=('王五',)).start() Thread(target=eat2,args=('马六',)).start() ''' 张三拿到筷子了 张三拿到面了 张三吃面 李四拿到面了 王五拿到筷子了 '''
递归锁可以解决死锁带来的问题。
from threading import RLock from threading import Thread import time kz = m = RLock() #筷子锁,面锁 def eat(name): kz.acquire() print('%s拿到筷子了'%name) m.acquire() print('%s拿到面了'%name) print('%s吃面'%name) m.release() kz.release() def eat2(name): m.acquire() print('%s拿到面了' % name) time.sleep(1) kz.acquire() print('%s拿到筷子了' % name) print('%s吃面' % name) kz.release() m.release() Thread(target=eat,args=('张三',)).start() Thread(target=eat2,args=('李四',)).start() Thread(target=eat,args=('王五',)).start() Thread(target=eat2,args=('马六',)).start()
八、信号量
信号量和线程池有什么区别?
相同点:在信号量acquire之后,和线程池一样,同时执行的只能有n个
不同点:开的线程数不一样,对于线程池来说,假设一直就只开5个线程,信号量有几个任务就开几个线程
from threading import Semaphore from threading import Thread import time import random def func(n,sem): sem.acquire() print('thread -%s start'%n) time.sleep(random.randint(1,3)) print('thread -%s done' % n) sem.release() sem = Semaphore(5) #一把锁有5把钥匙 for i in range(20): Thread(target=func,args=(i,sem)).start()
九、事件
事件的方法:
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
import time import random from threading import Event from threading import Thread def conn_mysql(): # 连接数据库 count = 1 while not e.is_set(): # 当事件的flag为False时才执行循环内的语句 if count>3: raise TimeoutError print('尝试连接第%s次'%count) count += 1 e.wait(0.5) # 一直阻塞变成了只阻塞0.5 print('连接成功') # 收到check_conn函数内的set指令,让flag变为True跳出while循环,执行本句代码 def check_conn(): ''' 检测数据库服务器的连接是否正常 ''' time.sleep(random.randint(1,2)) # 模拟连接检测的时间 e.set() # 告诉事件的标志数据库可以连接 e = Event() check = Thread(target=check_conn) check.start() conn = Thread(target=conn_mysql) conn.start()
十、条件
使得线程等待,只有满足某条件时,才释放n个线程
import threading def run(n): con.acquire() con.wait() # 等着 print("run the thread: %s" % n) con.release() if __name__ == '__main__': con = threading.Condition() # 条件 = 锁 + wait的功能 for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start() while True: inp = input('>>>') if inp == 'q': break con.acquire() # condition中的锁 是 递归锁 if inp == 'all': con.notify_all() else: con.notify(int(inp)) # 传递信号 notify(1) --> 可以放行一个线程 con.release()
十一、定时器
定时器:指定n秒后执行某个操作
import threading def run(n): con.acquire() con.wait() # 等着 print("run the thread: %s" % n) con.release() if __name__ == '__main__': con = threading.Condition() # 条件 = 锁 + wait的功能 for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start() while True: inp = input('>>>') if inp == 'q': break con.acquire() con.notify(int(inp)) # 传递信号 notify(1) --> 可以放行一个线程 con.release() print('****')
十二、线程队列
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 pq = queue.PriorityQueue() # 值越小越优先,值相同就asc码小的先出 pq.put((1,'z')) pq.put((1,'b')) pq.put((15,'c')) pq.put((2,'d')) print(pq.get()) print(pq.get()) print(pq.get()) ''' (1, 'b') (1, 'z') (2, 'd') '''
十三、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) 回调函数
开启线程需要成本,成本比开启进程要低
高IO的情况下,开多线程
所以我们也不能开启任意多个线程
futures.ThreadPoolExecutor # 线程池
futures.ProcessPoolExecutor # 进程池
import time import random from concurrent import futures def funcname(n): print(n) time.sleep(random.randint(1,3)) return n * '*' thread_pool = futures.ThreadPoolExecutor(5)
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import os,time,random def task(n): print('%s is runing' %os.getpid()) time.sleep(random.randint(1,3)) return n**2 if __name__ == '__main__': executor=ProcessPoolExecutor(max_workers=3) futures=[] for i in range(11): future=executor.submit(task,i) futures.append(future) executor.shutdown(True) print('+++>') for future in futures: print(future.result())
import time import random from concurrent import futures def funcname(n): print(n) time.sleep(random.randint(1,3)) return n * '*' thread_pool = futures.ThreadPoolExecutor(5) thread_pool.map(funcname,range(10)) # map,天生异步,接收可迭代对象的数据,不支持返回值