1、进程
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
-
- 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
- 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。
再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!
2、线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 进程要操作CPU,必须先创建线程。
- 所有在同一进程中的线程是共享同一块内存空间的。
2.1 线程的调用
2.1.1 直接调用
1 import threading 2 import time 3 4 5 def func(n): 6 print("task", n) 7 time.sleep(2) 8 9 10 # 用多线程来执行,同时打印task 1和task 2,然后等两秒钟程序结束。 11 t1 = threading.Thread(target=func, args=("1",)) # 生成一个线程实例 12 t2 = threading.Thread(target=func, args=("2",)) 13 t1.start() # 启动线程 14 t2.start()
2.2.2 继承式调用
1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self, n): 7 super(MyThread, self).__init__() 8 self.n = n 9 10 def run(self): # 定义每个线程要运行的函数,这里的函数必须是run 11 print("running my task", self.n) 12 time.sleep(2) 13 14 15 t1 = MyThread("1") # 实例化线程 16 t2 = MyThread("2") 17 18 t1.start() # 启动线程 19 t2.start() 20 21 print("finished")
2.2 join
主线程不会等子线程执行完毕才执行。如果子线程中存在sleep等影响运行速度的操作,则主线程不会等待子线程。
没有join
1 import threading 2 import time 3 4 5 def run(n): # 由子线程执行 6 print("task", n, threading.current_thread()) 7 time.sleep(2) 8 print("finished", n, threading.current_thread()) 9 10 11 for i in range(50): 12 t = threading.Thread(target=run, args=(i,)) 13 t.start() 14 15 print("不用等子线程执行完毕主线程继续执行", threading.current_thread(), threading.active_count()) # 会先运行子线程,打印task...;然后运行主线程,打印"不用等子线程执行完毕主线程继续执行";最后继续运行子线程,打印finished...
有join
1 import threading 2 import time 3 4 5 def run(n): 6 print("task", n) 7 time.sleep(2) 8 print("finished", n) 9 10 11 start_time = time.time() 12 threads_obj = [] # 该列表用来存放生成的线程 13 for i in range(50): 14 t = threading.Thread(target=run, args=(i,)) 15 t.start() # t为循环体变量,在循环结束后不会被释放。 16 threads_obj.append(t) # 为了不阻塞后面线程的启动,不在这里join,先放到一个列表里。 17 18 print("等待子线程执行完毕...") 19 20 for td in threads_obj: # 循环线程实例列表,等待所有线程执行完毕。 21 td.join() # join()作用是等所有子线程执行完毕后,主线程才继续执行。 22 23 print("等子线程执行完毕主线程才继续执行") 24 print("总耗时", time.time() - start_time) 25 # 打印结果:首先打印task...;接着打印"等待子线程执行完毕...";然后打印finished...;最后打印"等子线程执行完毕主线程才继续执行"和运行时间
2.3 守护线程
守护线程服务于非守护线程,非守护线程退出后整个程序就退出。
1 import threading 2 import time 3 4 5 def run(n): 6 print("task", n) 7 time.sleep(2) 8 print("finished", n) 9 10 11 for i in range(50): 12 t = threading.Thread(target=run, args=(i,)) 13 t.setDaemon(True) # 将t线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,t线程也会退出,由t启动的其它子线程会同时退出,不管是否执行完任务 14 t.start() 15 16 17 print("主线程执行完毕后,程序直接退出,不等守护线程的执行结果") # 不会打印finished...
2.4 互斥锁(Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
没有互斥锁的情况下:
1 # -*-coding:utf-8-*- 2 import threading 3 import time 4 5 nums = 0 6 7 8 def run(n): 9 global nums 10 time.sleep(2) 11 nums += 1 12 13 14 threads_obj = [] 15 for i in range(1000): 16 t = threading.Thread(target=run, args=(i,)) 17 t.start() 18 threads_obj.append(t) 19 20 for t in threads_obj: 21 t.join() 22 23 print("final nums:", nums)
在Python2中,发现运行结果没有得到预期的1000,而是比1000小,如下图:
而在Python3中,发现运行正常(Python3可能自动增加了锁来避免此情况),如下图:
为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都要对num进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=0这个初始变量交给cpu去运算,当A线程去处完的结果是1,但此时B线程运算完的结果也是1,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是1。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁,这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
增加互斥锁:
1 # -*-coding:utf-8-*- 2 import threading 3 import time 4 5 6 def run(n): 7 global nums # 每个线程都可以获取并修改该全局变量 8 time.sleep(1) 9 lock.acquire() # 获取锁 10 nums += 1 # 对全局变量进行操作运算 11 lock.release() # 释放锁 12 13 14 nums = 0 # 设定全局变量 15 lock = threading.Lock() # 生成全局锁 16 threads_obj = [] 17 for i in range(1000): 18 t = threading.Thread(target=run, args=(i,)) 19 t.start() 20 threads_obj.append(t) 21 22 for t in threads_obj: 23 t.join() 24 25 print("final nums:", nums)
增加线程锁后,分别在Python2和Python3下运行程序,结果如下:
3、进程与线程的区别
- 线程是执行的指令集,进程是资源的集合。
- 线程启动速度快,进程启动速度慢。但是运行后,没有可比性。
- 线程共享内存空间,进程的内存是相互独立的。
- 同一个进程的线程之间可以直接通信,进程之间通信必须通过中间代理进程来实现。
- 创建新线程很简单,创建新进程需要对其父进程进行一次克隆。
- 一个线程可以控制和操作同一进程里的其它线程,但是进程只能操作其子进程。
- 修改主线程有可能会影响其它线程的行为,但是对父进程的修改不会影响子进程。