一 什么是多线程与多进程
- 什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,一个线程即cpu需要执行的一条指令 - 什么是进程
一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)
一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程。
每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程 - 进程与线程的区别
1) 同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
2) 同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的。
3) 对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
4) 线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
5) 同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
6) 创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
7) 一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
8) 线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)
二 多线程
- 创建线程
在python中有专门的模块来创建线程,有(_thread和threading)这两个模块,_thread是低级模块,threading是高级模块,对 _thread进行了封装,绝大多数情况下,我们只需要使用 threading 这个高级模块
现在来创建一个线程:其实质就是把一个函数传入并创建 Thread实例,然后调用 start()开始执行
1> 这是创建线程的第一种方法:
import threading
def job():
print('这是一个需要执行的任务')
#激活的线程个数
print('当前的线程个数:',threading.active_count())
#打印当前线程的详细信息
print('当前线程信息:',threading.current_thread())
if __name__=='__main__':
t1=threading.Thread(target=job)
t1.start()
通过实例化对象来创建一个线程
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None)
1)在创建线程的时候,需要传的参数,其中target为函数名,name为定义的名称,如果不传值的话,默认为Thread-1,args为需要传入的参数,当然函数本身不需要传的话,这里也就不需要
2)threading模块中 active_count()是用来统计正在活跃的线程数的,由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,因此当前的线程数为2
3)而current_thread()是用来显示当前线程的详细信息的,里面包含名称和启动的时间
2> 这是创建线程的第二种方法:
通过继承threading.Thread来自定义线程类,其本质是:重构Thread类中的run方法
import threading
#类的继承
class IpThread(threading.Thread):
#重写构造方法
def __init__(self,jobname):
super(IpThread,self).__init__()
self.jobname=jobname
#将多线程需要执行的任务重写到run方法中
def run(self):
print('正义')
t1=IpThread(jobname='new_job')
t1.start()
如果你要在run方法中传递参数的话,那就将参数通过构造函数与self绑定在一起
- 多线程中的join方法
1) 不使用多线程
import time
#不使用线程
def music(name):
for i in range(2):
print('正在听音乐: %s' %(name))
time.sleep(2)
def code(name):
for i in range(2):
print('正在写代码: %s' %(name))
time.sleep(3)
if __name__=='__main__':
start_time=time.time()
music('神话')
code('爬虫')
print('花费的时间: %s' %(time.time()-start_time))
事情是一个一个的做,没有同时进行,但是我敲代码的时候是会听歌的,
因此此处可以使用多线程来处理的
2) 使用多线程
import threading
import time
#使用线程
def music(name):
for i in range(2):
print('正在听音乐: %s' %(name))
time.sleep(2)
def code(name):
for i in range(2):
print('正在写代码: %s' %(name))
time.sleep(3)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('神话',))
t2=threading.Thread(target=code,args=('爬虫',))
t1.start()
t2.start()
print('花费的时间: %s' %(time.time()-start_time))
发现时间确实是变快了好多,但是感觉跟想象的不太一样,执行时间都结束了,而事情却没做完,这是因为主线程阻塞了子线程进行
这里就需要引入join方法了
3) 引入join方法
import threading
import time
#使用线程
def music(name):
for i in range(2):
print('正在听音乐: %s' %(name))
time.sleep(2)
def code(name):
for i in range(2):
print('正在写代码: %s' %(name))
time.sleep(3)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('神话',))
t2=threading.Thread(target=code,args=('爬虫',))
t1.start()
t2.start()
#等待所有的子线程执行结束之后,继续执行主线程的内容
t1.join()
t2.join()
print('花费的时间: %s' %(time.time()-start_time))
现在的执行时间是正确的了
其实join的本质就是:等待所有的子线程执行结束之后,再去执行主线程的内容
- 守护线程setDaemon方法
守护线程的意义:为了让主线程在执行结束时,强制结束未执行完的子线程
import threading
import time
#使用线程,让其主线程执行结束后,强制结束所有的子线程
def music(name):
for i in range(2):
print('正在听音乐: %s' %(name))
time.sleep(2)
def code(name):
for i in range(2):
print('正在写代码: %s' %(name))
time.sleep(3)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('神话',))
t2=threading.Thread(target=code,args=('爬虫',))
#将子线程声明为守护线程,如果设置为True,子线程启动后,当主线程执行结束,子线程也结束
#设置setDaemon必须在线程启动前
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
print('花费的时间: %s' %(time.time()-start_time))
可以看到主线程在结束时,未完成的子线程也结束了
- 线程锁lock
多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱
下面来看一个实例:
import threading
def add():
global money
for i in range(1000000):
money+=1
def reduce():
global money
for i in range(1000000):
money-=1
if __name__ == '__main__':
money=0
t1=threading.Thread(target=add)
t2=threading.Thread(target=reduce)
t1.start()
t2.start()
t1.join()
t2.join()
print('最终金额: %s'%(money))
理论上money的值应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,money的结果就不一定是0了
原因是因为:高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:money+=1 也是分为两步去完成的 先计算 money+1,将结果存入临时变量,再将临时变量赋值给money,而两个线程都拥有各自的临时变量,当t1与t2交替执行的时候,就会导致把同一个对象给改的混乱
这里我们为了避免这样的情况发生,从而引入了线程锁lock 这个概念
def add(lock):
# 操作变量之前进行加锁
lock.acquire()
global money
for i in range(1000000):
money+=1
# 操作变量之后进行解锁
lock.release()
def reduce(lock):
lock.acquire()
global money
for i in range(1000000):
money-=1
lock.release()
if __name__ == '__main__':
money=0
# 实例化一个锁对象
lock=threading.Lock()
t1=threading.Thread(target=add,args=(lock,))
t2=threading.Thread(target=reduce,args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
print('最终金额: %s'%(money))