一、什么是线程。什么是进程。
一个应用程序:软件
一个应用程序:可以创建多个进程(默认一个进程),一个进程可以创建多个线程(默认一个线程)
线程:工作的最小单元,共享进程中的所有资源,每个线程分担一点任务,最终完成最后的结果
进程:独立开辟内存 进程之间的数据隔离,最小资源单位
总结: 1.操作系统帮助开发者操作硬件
2.程序员写好代码在操作系统上运行
任务特别多:
3.串行 一个个的去执行
1 写好代码
2 交给解释器运行
3 解释器读取代码,再交给操作系统执行,根据写的代码选择创建线程/进程去执行(单进程/单线程)
4.多线程的话
1 写好代码
2 交给解释器运行
3 解释器读取代码,再交给操作系统执行,根据写的代码选择创建线程/进程去执行(单进程/多线程)
GIL:Python内置的全局解释器锁:用于限制一个进程中同一时刻只有一个线程被cpu调度
线程之间切换默认gil锁在执行100个cpu指令的时候就切换 还有一个就是过期时间
为什么还保留这把锁?:对于语言的创始人来说:在开发这门语言时候,目的是最开始为了方便快速的把这个语言开发出来,假如没有这把锁处理的事情非常多,比如一个进程里边有三个线程同时被CPU调度,那么这三个线程同时修改一个值,这里就要处理,还有就是线程执行到一半要终端,那么就要记住本次线程操作的状态下次回来执行这个线程的时候继续从这里开始执行。
线程创建的越多越好吗?不是的 线程之间进行切换,要进行上下文管理CPU分片处理要记录线程运行状态
1 import sys 2 3 v = sys.getcheckinterval() 4 print(v)
IO操作不占用CPU
Python多线程情况下:
计算密集型操作:效率低(GIL锁)
IO密集型操作:效率高
Python多进程情况下:
计算密集型操作:效率高(浪费资源)不得已而为之
IO密集型操作:效率高 (浪费资源)
Java多线程情况下:
计算密集型操作:效率高
IO密集型操作:效率高
Java多进程程情况下:(Java程序员一般不写多进程)
计算密集型操作:效率高
IO密集型操作:效率高
自己画了图,图解非常清晰很容易理解(对比Java)
实例一:主线程默认等子线程执行完毕
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import threading 5 import time 6 7 def func(arg): 8 time.sleep(arg) 9 print(arg) 10 11 12 t1 = threading.Thread(target=func, args=(3,)) 13 t1.start() 14 15 t2 = threading.Thread(target=func, args=(9,)) 16 t2.start() 17 18 print('123')
执行结果:
123 3 9
实例二、主线程不再等子线程,主线程终止则所有子线程终止
1 # def func(arg): 2 # time.sleep(arg) 3 # print(arg) 4 # 5 # 6 # t1 = threading.Thread(target=func, args=(3,)) 7 # t1.setDaemon(True) # 不等子线程 8 # t1.start() 9 # 10 # t2 = threading.Thread(target=func, args=(9,)) 11 # t2.setDaemon(True) 12 # t2.start() 13 # 14 # print(123) 15 16 # 123
实例三、开发者可以控制主线程等待子线程(最多等待时间)
1 # def func(arg): 2 # time.sleep(10) 3 # print(arg) 4 # 5 # 6 # print('创建子线程t1') 7 # t1 = threading.Thread(target=func, args=(3,)) 8 # t1.start() 9 # t1.join() # 让主线程在这里等着,等到子线程t1执行完毕继续往下走 10 # # t1.join(1) # 主线程最多等1s 11 # print('创建子线程t2') 12 # t2 = threading.Thread(target=func, args=(9,)) 13 # t2.start() 14 # t2.join() # 让主线程在这里等着,等到子线程t2执行完毕继续往下走 这样搞纯粹没意义了开线程没卵用就是串行了 15 # # t2.join(1) # 主线程最多等1s 16 # print(123) 17 # 创建子线程t1 18 # 3 19 # 创建子线程t2 20 # 9 21 # 123
实例四、获取线程名称
1 # def func(arg): 2 # # 获取当前执行该函数的线程的名称 3 # t = threading.current_thread() 4 # name = t.getName() 5 # print(name, arg) 6 # 7 # 8 # t1 = threading.Thread(target=func, args=(3,)) 9 # t1.setName('t111111') 10 # t1.start() 11 # 12 # 13 # t2 = threading.Thread(target=func, args=(9,)) 14 # t2.setName('t222222') 15 # t2.start() 16 # 17 # print(123)
实例五、线程本质
1 # 先打印 3?还是123? 这个不确定 要看cpu现在忙不忙,有没有空余时间 2 # def func(arg): 3 # print(arg) 4 # 5 # 6 # t1 = threading.Thread(target=func, args=(3,)) 7 # t1.start() # 是开始运行线程吗? 不是 8 # # start告诉cpu 我已经准备好了,你可以调度我了 9 # 10 # 11 # print(123)
实例六、面向对象版本的线程
自己写run方法让自己的线程去执行自己想执行的任务,就不走Thread里边内置的run方法了
1 class MyThread(threading.Thread): 2 # 看源码发现的 3 """ 4 self._target = target 5 if self._target: 6 self._target(*self._args, **self._kwargs) 7 """ 8 def run(self): 9 print('11111', self._args, self._kwargs) 10 11 12 # def func(arg): 13 # print(arg) 14 15 16 # t1 = MyThread(target=func, args=(11,)) 17 t1 = MyThread(args=(11,), kwargs={'name': 1}) 18 t1.start() 19 # 11111 (11,) {'name': 1}
实例七:多线程的问题
如果现在要对全局的一个变量值进行修改,那么线程拿到的数据有可能不安全的
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import time 5 import threading 6 7 lock = threading.Lock() 8 n = 5 9 10 11 def func(i): 12 print('这段代码不加锁', i) 13 14 lock.acquire() # 加锁 此区域的代码同一时刻只能一个线程执行 15 global n 16 print('当前线程', i, '读取到n的值', n) 17 n = i 18 time.sleep(1) 19 print('当前线程', i, '修改n的值', n) 20 lock.release() # 释放锁 21 22 23 for i in range(5): 24 t = threading.Thread(target=func, args=(i,)) 25 t.start()
执行结果:
1 这段代码不加锁 0 2 当前线程 0 读取到n的值 5 3 这段代码不加锁 1 4 这段代码不加锁 2 5 这段代码不加锁 3 6 这段代码不加锁 4 7 当前线程 0 修改n的值 0 8 当前线程 1 读取到n的值 0 9 当前线程 1 修改n的值 1 10 当前线程 2 读取到n的值 1 11 当前线程 2 修改n的值 2 12 当前线程 3 读取到n的值 2 13 当前线程 3 修改n的值 3 14 当前线程 4 读取到n的值 3 15 当前线程 4 修改n的值 4 16 17 这段代码不加锁 0 18 当前线程 0 读取到n的值 5 19 这段代码不加锁 1 20 当前线程 1 读取到n的值 0 21 这段代码不加锁 2 22 当前线程 2 读取到n的值 1 23 这段代码不加锁 3 24 当前线程 3 读取到n的值 2 25 这段代码不加锁 4 26 当前线程 4 读取到n的值 3 27 当前线程 2 修改n的值 4 28 当前线程 1 修改n的值 4 29 当前线程 0 修改n的值 4 30 当前线程 4 修改n的值 4 31 当前线程 3 修改n的值 4