线程
线程被称作轻量级进程。与进程类似,不过它们是在同一个进程下执行的。
并且它们会共享相同的上下文。每一个进程中至少有一个线程。
顾名思义,线程就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程
所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。(一个进程里面开多个线程(共享同一个进程里面的内存空间))
进程
- 计算机中最小的资源分配单位
- 进程对于操作系统来说还是有一定的负担的
- 创建一个进程,操作系统要分配的资源大致有:代码,数据,文件
线程:
- 线程是计算机中被CPU调度的最小单位(计算机中CPU都是执行线程中的代码)
- 轻量级的概念
- 没有属于自己的进程资源(一条线只负责执行代码,没有自己独立的代码,变量,文件资源)
- 同一个进程中的所有线程的资源都是共享的
- 线程启动的速度快
- 多线程一定是在一个进程里面开启的,共享进程里面的资源
联系:
每个进程中至少有一条线程在工作。
同一进程下的多个线程共享进程的资源,而多个进程之间内存空间是隔离的
进程和线程的关系
Python GIL锁(CPython解释器)
Python在设计的时候,还没有多核处理器的概念。
因此,为了设计方便与线程安全,直接设计了一个锁。
这个锁要求,任何进程中,一次只能有一个线程在执行。
因此,并不能为多个线程分配多个CPU。所以Python中的线程只能实现并发,而不能实现真正的并行。
但是Python3中的GIL锁有一个很棒的设计,在遇到阻塞(不是耗时)的时候,会自动切换线程。因此我们可以利用这种机制来有效的避开阻塞~充分利用CPU
多线程用于IO密集型,如socket,爬虫,web
python中一个进程为中的多个线程为什么不能并行?
因为python是一个解释型语言,所有的解释型语言都不可以并行。由于cpython解释器的原因, 内部有一把全局解释器锁, 所有的线程都不能充分的利用多核, 同一时刻同一个线程中的线程只有一个能被CPU执行。
这把锁就叫GIL锁。GIL锁虽然是限制了你的程序效率, 但是目前能够帮助你在线程中的切换中提高效率。
Python线程模块的选择
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
threading模块
线程的创建Threading.Thread类
线程的创建

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=('luffy',)) 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('luffy') t.start() print('主线程')
多进程和多线程
多线程与多进程的PID

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) print('主线程/主进程pid',os.getpid()) t1.start() t2.start() #part2:开多个进程,每个进程都有不同的pid p1=Process(target=work) p2=Process(target=work) print('主线程/主进程pid',os.getpid()) p1.start() p2.start() >>> 主线程/主进程pid 8772 hello 8772 hello 8772 主线程/主进程pid 8772 hello 6176 hello 11064

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 '''

from threading import Thread from multiprocessing import Process import os def work(): global n n=0 if __name__ == '__main__': # n=100 # p=Process(target=work) # p.start() # p.join() # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100 n=1 t=Thread(target=work) t.start() t.join() print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据 同一进程内的线程共享该进程的数据?
多线程实现socket

import multiprocessing import threading import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5) def action(conn): while True: data=conn.recv(1024) print(data) conn.send(data.upper()) if __name__ == '__main__': while True: conn,addr=s.accept() p=threading.Thread(target=action,args=(conn,)) p.start()

import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue s.send(msg.encode('utf-8')) data=s.recv(1024) print(data)
Thread类的其他方法

Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
守护线程
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
#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=('luffy',)) t.setDaemon(True) #必须在t.start()之前设置 t.start() print('主线程') print(t.is_alive()) ''' 主线程 True '''

import time from threading import Thread def func1(): while True: time.sleep(0.5) print(123) def func2(): print('func2 start') time.sleep(3) print('func2 end') t1 = Thread(target=func1,) t2 = Thread(target=func2) t1.setDaemon(True) t1.start() t2.start() print('主线程的代码结束了') >>> func2 start 主线程的代码结束了 123 123 123 123 123 func2 end
信号量
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有5个任务拿到锁去执行。
如果说互斥锁是合租房屋的人去抢一个厕所,那么信号量就相当于一群路人争抢公共厕所,公共厕所有多个坑位,这意味着同一时间可以有多个人上公共厕所,但公共厕所容纳的人数是一定的,这便是信号量的大小。
import time from threading import Semaphore,Thread def func(index,sem): sem.acquire() print(index) time.sleep(1) sem.release() if __name__ == '__main__': sem = Semaphore(5) # 设置信号量为5,即同时会有5个任务抢到锁 for i in range(10): Thread(target=func,args=(i,sem)).start()
原理
1. Semaphore管理一个内置的计数器
2. 每当调用acquire()时内置计数器-1
3. 调用release() 时内置计数器+1
4. 计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
事件
同进程的一样
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。
在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
import time import random from threading import Event, Thread def check(e): print('开始检测数据库连接') time.sleep(random.randint(1, 5)) e.set() def connect(e): for i in range(3): e.wait(0.5) if e.is_set(): print('数据库连接成功') break else: print('尝试连接数据库%s次失败'%(i+1)) else: raise TimeoutError e = Event() Thread(target=connect, args=(e, )).start() Thread(target=check, args=(e, )).start()
条件
使得线程等待,只有满足某条件时,才释放n个线程
# notify 控制流量 通知有多少人可以通过了 # wait 在门口等待的所有人 # acquire # wait 使用前后都需要加锁 # 做的事情 # release # acquire # notify 使用前后都需要加锁 # release from threading import Condition,Thread def func(con,index): print('%s在等待'%index) con.acquire() con.wait() print('%s do something'%index) con.release() con = Condition() for i in range(10): t = Thread(target=func,args=(con,i)) t.start() con.acquire() # con.notify_all() con.notify(3) con.release()
定时器
定时器,指定n秒后执行某个操作
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed