本文的大概内容从一片技术文章中而来,再加上自己的想法。
进程
在系统中,一个任务就是一个进程,比如开启浏览器,打开微信,每打开一个任务,代表在系统中启动了一个进程,进程代表着一个资源的集合。
线程
线程是操作系统能够运行的最小度量单位,他被包含在进程当中,是进程中实际的运行单位。
例:打开浏览器,打开多个TAB页面时就是启动了多个线程。
线程与进程的区别
进程 : 对各种资源管理的集合
线程 : 操作系统最小的调度单位,是一串指令的集合
进程不能单独执行,它只是资源的一个集合,如果进程想要操作CPU,进程必须先创建一个线程,在进程中的所有线程,都同享同一块内存空间。进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的,进程有父进程、子进程,独立的内存空间,唯一的进程标识符【pid】(注:启动线程比启动进程快,线程的内存空间是共享的,进程的空间是独立的。)
在Python中,如果想要使用多线程编程,需要导入threading模块。
import threading
import time
def talk(name, age):
print('%s说:今年我%s岁' % (name, age))
time.sleep(2)
print('%s说:介绍完毕' % name)
# def talk(*args, **kwargs):
# print('%s说:今年我%s岁' % (args[0], kwargs['age']))
# time.sleep(2)
# print('%s说:介绍完毕' % args[0])
t1 = threading.Thread(target=talk, args=('大师兄',), kwargs={'age': 19}, name='线程1', group=None)
t2 = threading.Thread(target=talk, args=('牛教授',), kwargs={'age': 45}, name='线程2')
t1.start()
t2.start()
在启动多线程时,target代表你要启动的多线程运行的函数(例:比如我有一个talk的函数,两人希望同时说话,把函数名赋值给target就可以了),args代表多线程执行的函数所需要的参数(例:talk的函数谁来说话,name就可以通过args传递给talk的函数。注:这里args如果传递多个参数时,它接收的是元组,如果是一个参数时,我们需要写逗号。),kwargs同样是多线程执行时函数所需要的参数(接受的是一个字典),name代表这个线程的名字,group代表线程组(Python还没有实现,所以默认必须穿None或不写)
threading与实例对象提供了几个方法可以让我们更直观的学习线程。
threading.active_count() # 返回当前运行的线程个数
threading.enumerate() # 返回当前运行中的线程list
threading.current_thread() # 返回当前的线程变量
t1.start() # 启动线程
t1.is_alive() # 判断线程是否在运行 运行指启动后、终止前。
t1.getName() # 获取线程名
t1.setName('填写更改后的名称') # 对线程进行命名
t1.setDaemon(True) # 设置守护线程
t1.isDaemon() # 判断是否是守护线程
t1.join(timeout=20) # 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)
在Python中还有一种通过继承来完成的多继承(了解就好)
import threading
import time
class MyThread(threading.Thread):
def __init__(self, name):
super(MyThread, self).__init__()
self.name = name
def run(self):
print(self.name, '说开始')
time.sleep(2)
print(self.name, '说结束')
t1 = MyThread('大师兄')
t2 = MyThread('牛教授')
t1.start()
t2.start()
通过继承threading.Thread并覆盖run方法完成多线程,比我们通过def实现的多继承有局限性,我们在类中定义的run方法,不是随意定义的,函数名必须为run,否则不会重写Thread的run方法,程序不会执行多线程。
启动多线程时,需要等待这个线程执行完成,主线程在继续执行时我们就要用到join(阻塞主线程,挡住,无法执行join以后的语句,专,注执行多线程。)
import threading
import time
def talk(name):
print(name, '说开始')
time.sleep(2)
print(name, '说结束')
t_list = []
for i in range(1, 4):
t = threading.Thread(target=talk, args=('大师兄%s' % i,))
t.start()
t_list.append(t)
start = time.time()
for t in t_list:
t.join()
end = time.time() - start
print(end)
print(threading.active_count())
join分为有参数和无参数两种
多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。
无参数时,需要等待线程执行结束后,才可以继续执行主进程
设置参数后,则等待该线程这么长时间就不管它了(而该线程并没有结束)。不管的意思就是可以执行后面的主进程了。
import threading
import time
def talk(name):
print(name, '说开始')
time.sleep(2)
print(name, '说结束')
t_list = []
for i in range(1, 4):
t = threading.Thread(target=talk, args=('大师兄%s' % i,))
t.start()
t_list.append(t)
start = time.time()
for t in t_list:
t.join(0.1)
end = time.time() - start
print(end)
print(threading.active_count())
在设置了join的timeout参数后,每一个线程在等待0.1秒后,继续执行主线程。这时的end值为0.4左右。也就证明了,当设置了join的参数后,等待线程0.1秒后继续执行了主线程。
守护线程(setDaemo)
守护线程,代表当主线程执行完成后,所有守护线程立即结束执行。(例:我们可以把主线程比喻为古代的皇帝,而守护线程则比喻为古代皇帝的妃子,如果皇帝(主线程)死亡,妃子(子线程)需要陪葬。)
import threading
import time
def talk(name):
print(name, '说开始')
time.sleep(2)
print(name, '说结束')
for i in range(1, 4):
t = threading.Thread(target=talk, args=('大师兄%s' % i,))
t.setDaemon(True)
t.start()
print(threading.active_count())
上面这段代码虽然我们启动了talk函数的说的方法,但是我们在方法中sleep了2秒钟,同时我们又对每一个启动的子线程设置了守护线程,这时当主线程执行完4个子线程的启动后,打印当前执行的线程数后,主线程结束执行,同时子线程跟着也就结束执行,所以没有打印到最后的xxx说结束,因为主线程已经带着子线程一起死了。
锁
在多线程中,如果启动多个线程操作同一份数据,可能会导致数据错乱。这时我们就要加上锁,来控制保证这份数据每次只允许一个线程操作。
import threading
lock = threading.Lock()
num = 0
def write():
global num
lock.acquire()
num += 1
lock.release()
t_list = []
for i in range(1, 100):
t = threading.Thread(target=write, )
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
我们通过acquire(timeout=2)和release来控制上锁和解锁(注:这里在上锁后一定要记得将锁解开,否则后面的线程无法操作同一份资源。这里在Python3中不会出现操作数据异常的情况,Python3默认加锁了,在Python2中会出现数算不准的情况)
RLock(递归锁)
import threading, time
lock = threading.RLock()
def talk():
print('准备获得锁')
if lock.acquire():
print('获得第一个锁')
if lock.acquire():
print('获得第二个锁')
time.sleep(1)
# lock.release()
# print('解除第二个锁')
time.sleep(1)
lock.release()
print('解除第一个锁')
print('没有锁')
t = threading.Thread(target=talk, )
t1 = threading.Thread(target=talk)
t.start()
t1.start()
实验
通过多线程来体现爬虫的效率
import threading
import requests, time
urls = {
"blog": 'http://www.imdsx.cn',
"besttest": 'http://www.besttest.cn',
"taobao": "http://www.taobao.com",
"jd": "http://www.jd.com",
}
def run(name, url):
res = requests.get(url)
with open(name + '.html', 'w', encoding=res.encoding) as fw:
fw.write(res.text)
start_time = time.time()
lis = []
for url in urls:
t = threading.Thread(target=run, args=(url, urls[url]))
t.start()
lis.append(t)
for t in lis:
t.join()
end_time = time.time()
print('run time is %s' % (end_time - start_time))
多进程
Python中多进程与多线程的代码格式相同,Python的进程需要使用multiprocessing模块。多进程要比多线程耗费的资源大,进程的数据是独立的,所以每启动一个进程都会生成一份内存来存储进程的数据。多进程实质就是启动每个进程中的默认线程去完成任务。
from multiprocessing import Process
import time
def talk(name):
print("%s说:进程和线程的代码方式相同。" % name)
time.sleep(1)
if __name__ == '__main__':
names = ['dsx', 'nn', 'ads']
pro = []
for item in names:
p = Process(target=talk, args=(item,))
p.start()
pro.append(p)
for item in pro:
item.join()