系列文章目录
python基础①-基础环境搭建和工具使用
python基础②-常用的各种数据类型初认知
python基础③-数据类型常用的操作和方法字符串、数值、bool
python基础④-数据类型常用的操作和方法列表、元组、字典、集合
python基础⑤-控制流程
python基础⑥-函数
python基础⑦-字符编码与文件操作
python基础⑧-异常
python基础⑨-迭代器和生成器
python基础⑩-面向对象
python基础⑪-继承与派生
python基础⑫-多进程
python基础⑬-多线程
python基础⑭-进程池、线程池、协程
python基础⑮-网络编程socket
python基础⑯-网络编程socket进阶
什么是线程
1 什么是线程
进程其实一个资源单位(开启一个内存空间,里面放应该执行的代码,代码运行产生的数据),
而进程内的线程才是cpu上的执行单位
进程是资源单位 qq资源 相当于一个车间 造发动机和造轮胎能共享吗
线程其实指的就是代码的执行过程(开空间没关系,数据往进程去要)
线程就是车间流水线 跟车间要
至少有一个线程 一个车间也可以有多少流水线
2 为何要用线程
线程vs进程
1. 同一进程下的多个线程共享该进程内的资源
2. 创建线程的开销要远远小于进程
并发2种
多进程实际上是每个进程里面单独一个线程
由于进程当中资源不共享
并发多个任务需要通信需要利用管道或者队列
多线程是指同一个进程里面多个线程
本身同一个进程里面多个线程资源就共享
所以不需要借助任何的机制,数据之间就可以交互
3 如何用线程
线程
开启线程
from threading import Thread
import time
def task(name):
print('%s is running'%name) # 2
time.sleep(2)
print('%s is done'%name)
if __name__ == '__main__':
t=Thread(target=task,args=('线程1',))
# 造线程非常快,因为不用开辟空间了
t.start()
print('主') # 1
线程特性
线程的物理空间(内存)可以共享
from threading import Thread,active_count,current_thread
import time,os
n = 100
def task():
global n
print('%s is running'%os.getpid())
print('子%s'%current_thread().name)
n = 0
time.sleep(3)
if __name__ == '__main__':
t=Thread(target=task)
t.start()
# 等待子线程运行完
t.join()
# 线程的个数
print(active_count())
print(n)
# 线程所在的进程pid
print('主%s'%os.getpid())
# 线程的名字
print('主%s'%current_thread().name)
守护线程
所以守护线程需要等到非守护的所有线程都死了才死
主线程就是一个守护线程
主线程掌管了这个进程里面的资源
主线程不会死了,资源是来自主线程的
主线程是这个进程里面的老大
等待所有的子线程死了才死
# 主线程要等到所有的子线程死了才死
from threading import Thread
import time
def task(name):
print('%s is running' % name) # 2
time.sleep(2)
print('%s is done' % name)
if __name__ == '__main__':
t = Thread(target=task, args=('线程1',))
# 造线程非常快,因为不用开辟空间了
# t.daemon = True
t.start()
print('主') # 1
from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(1)
print('end123')
def bar():
print(456)
time.sleep(3)
print('end456')
if __name__ == '__main__':
# # 进程
# t1=Process(target=foo)
# t2=Process(target=bar)
# t1.daemon = True
# # # 创建进程的开销非常大,看你机器的配置,配置好123看得到
# t1.start()
# t2.start()
# print('主')
# 线程
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print('主')
# 因为456线程没有死掉
# 守护线程 》》主线程 》》非守护线程
# 按时下班的员工》》老板 》》加班的员工
线程互斥锁
为什么要有互斥锁
当多个线程访问共享资源(例如下面访问同一个n)可能会出现线程并发问题
原因是因为cpu可能随时切换任务,多个指令如果没有原子性保障就可能出现线程并发安全
from threading import Thread,Lock
import time
mutex=Lock()
n = 100
def task():
global n
# 线程1加锁
# mutex.acquire()
temp = n
# 在这个时间消耗完之前,后面的99个线程都进来了
# 并且拿到的是temp=100
# 效率高了,不安全
# IO操作切换线程
time.sleep(0.1)
n = temp-1
# 线程1计算完释放锁
# mutex.release()
if __name__ == '__main__':
t_1 = []
start = time.time()
for i in range(100):
t = Thread(target=task)
t_1.append(t)
t.start()
for t in t_1:
t.join()
print(n,time.time()-start)
此处就导致了n最后为99。理论上应该为0
怎么解决并发问题
from threading import Thread, Lock
import time
mutex = Lock()
n = 100
def task():
global n
# 线程1加锁
mutex.acquire()
temp = n
# 在这个时间消耗完之前,后面的99个线程都进来了
# 并且拿到的是temp=100
# 效率高了,不安全
# IO操作切换线程
time.sleep(0.1)
n = temp - 1
# 线程1计算完释放锁
mutex.release()
if __name__ == '__main__':
t_1 = []
start = time.time()
for i in range(100):
t = Thread(target=task)
t_1.append(t)
t.start()
for t in t_1:
t.join()
print(n, time.time() - start)
mutex.acquire()
mutex.release()
这两个方法中间的代码永远保证只有一个线程执行到。如果执行的线程没执行完。其他线程是没法进来执行的。从而保证了原子性。保证了 线程安全
GIL 全局解释器锁
- 什么是GIL(全局解释器锁)
互斥锁就是把多个任务的共享数据的修改由并发变成串行
代码运行先拿到cpu的权限,还需要把代码丢给解释器,再在进程里面的线程运行
GIL本质就是一把互斥锁,相当于执行权限
每个进程内都会存在一把GIL,同一进程内的多个线程
必须抢到GIL之后才能使用解释器来执行自己的代码,
即同一进程下的多个线程无法实现并行,
用不了多核(多个cpu)优势
但是可以实现并发
因为多线程是遇到io操作就会释放GIL锁- 为何要有GIL
垃圾回收机制不是线程安全的
每个进程内都会存在一把GIL
意味着有锁才能计算
多进程适合处理计算密集型
多线程适合处理io密集型 所以多线程多核优势没有意义- 如何用GIL
有了GIL,应该如何处理并发
也就是说如果没有io操作的话。同一个进程里面开启多个线程没有意义。因为一个线程拿到了GIL锁后会一直执行完所有的计算型操作后才会释放锁
跟java中的多线程不太一样
计算密集型:应该使用多进程
from multiprocessing import Process
from threading import Thread
import os, time
def work():
res = 0
for i in range(10000000):
res *= i
if __name__ == '__main__':
l = []
# 看到cpu个数
# print(os.cpu_count())
start = time.time()
for i in range(8):
# 多进程8个cpu同时在算,计算效率高,但是进程之间切换效率低
# p = Process(target=work)
# # 多线程是1个cpu在计算
# # 毕竟计算效率低,但是切换效率高
p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
print('主%s' % (time.time() - start))
IO密集型: 应该开启多线程
from multiprocessing import Process
from threading import Thread
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l = []
start = time.time()
for i in range(20):
# p = Process(target=work)
p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
print('主%s'%(time.time()-start))
线程queue
# 虽然线程中数据共享,但是队列可以处理锁的问题
import queue
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
# q.put(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get())