Python学习--多线程&多进程

一、线程&进程

1、进程

在系统中,一个任务就是一个进程。比如开启浏览器,打开微信,打开两个记事本就是启动了两个记事本进程。每打开一个任务,代表在系统中启动了一个进程,进程代表着一个资源的集合

2、线程(Thread)

线程是操作系统能够运行的最小度量单位,他被包含在进程当中,是进程中实际的运行单位。

有些进程不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。线程是最小的执行单元,而进程由至少一个线程组成。

比如我们做事一个人做是比较慢的,如果多个人一起来做的话,就比较快了。程序也是一样的,我们想运行的速度快一点的话,就得使用多进程,或者多线程,在python里面,多线程被很多人诟病,为什么呢,因为Python的解释器使用了GIL的一个叫全局解释器锁,它不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。

3、进程与线程的区别

进程 : 对各种资源管理的集合

线程 : 操作系统最小的调度单位,是一串指令的集合

进程不能单独执行,它只是资源的一个集合,如果进程想要操作CPU,进程必须先创建一个线程,在进程中的所有线程,都同享同一块内存空间。进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的,进程有父进程、子进程,独立的内存空间,唯一的进程标识符【pid】(注:启动线程比启动进程快,线程的内存空间是共享的,进程的空间是独立的。)

二、多线程的使用(threading模块)

import threading
import time

def talk(name, age): #定义每个线程要运行的函数
    print('%s说:今年我%s岁' % (name, age))
    time.sleep(5)
    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=('小黑','19')) #生成一个线程实例
t2 = threading.Thread(target=talk, args=('阿根',),kwargs={'age': 45}, name='线程2',group=None)
# target代表你要启动的多线程运行的函数,把函数名赋值给target即可
# args代表多线程执行的函数所需要的参数,
# 注:这里args如果传递多个参数时,它接收的是元组,如果是一个参数时,我们需要写逗号
# kwargs同样是多线程执行时函数所需要的参数(接受的是一个字典)
# name代表这个线程的名字,
# group代表线程组(Python还没有实现,所以默认必须穿None或不写)

t1.start() #启动线程
t2.start() #启动另一个线程

还有一种通过继承启动多线程(两种方式没有区别只是两种写法,这种更复杂了解就好)

通过继承threading.Thread并覆盖run方法完成多线程,比我们通过def实现的多继承有局限性,我们在类中定义的run方法,不是随意定义的,函数名必须为run,否则不会重写Thread的run方法,程序不会执行多线程。

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与实例对象提供了几个方法:

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(可选参数)

三、线程等待

多线程在运行的时候每个线程都是独立运行的,不受其他的线程干扰。如果想在哪个线程运行完之后,再做其他操作的话,就得等待它完成,使用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())

四、守护线程 

守护线程,当主线程执行完成后,所有守护线程立即结束执行。例:一个国王(非守护线程)有很多仆人(守护线程),国王死后它的仆人也得跟着陪葬

#------守护线程-------
#守护线程就是和秦始皇陪葬的人一样
#主线程就是秦始皇
#子线程就是陪葬的人。

import threading,time

def run():
    time.sleep(9)
    print('run。。。')

for i in range(10):
    t = threading.Thread(target=run)
    t.setDaemon(True) #设置子线程成为一个守护线程
    t.start()
print('over..')
#结果是:over..
#当主线程执行完后,不管子线程结没束结束都跟着主线程一起结束了

#实际场景:qq、浏览器,关闭浏览器时所有的tab页都会被关闭
#在实际写代码的过程中还没有发现有需要用守护线程的场景

五、线程锁

# 线程锁就是:
# 很多线程一起在操作一个数据的时候,可能会有问题,
# 就要把这个数据加个锁,同一时间只能有一个线程操作这个数据。

import threading
from threading import Lock

num = 0
lock = Lock() #实例化一把锁,先申请一把锁
def run():
    global num
    # lock.acquire() #加锁
    # num+=1
    # lock.release()  #解锁,上锁后一定要记得将锁解开,否则后面的线程无法操作同一份资源,就会导致死锁
 with lock: #自动加锁解锁,python3会自动加锁,但为了保险最好还是加上锁;python2里面就需要手动加锁 num+=1 for i in range(100): t = threading.Thread(target=run) t.start() while threading.active_count()!=1: pass #判断当前活动的线程是几个,如果是1的话, #说明子线程都已经执行完成了。 print(num)

练习:通过一个简单的爬虫,看下多线程的效果

import threading
import requests, time

urls = {
    "baidu": 'http://www.baidu.com',
    "blog": 'http://www.nnzhp.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))

# 下面是单线程的执行时间
# start_time = time.time()
# for url in urls:
#     run(url,urls[url])
# end_time = time.time()
# print('run time is %s'%(end_time-start_time))

六、多进程(multiprocessing)

多进程要比多线程耗费的资源大,进程的数据是独立的,所以每启动一个进程都会生成一份内存来存储进程的数据。多进程实质就是启动每个进程中的默认线程去完成任务。

# Python里面的多线程,是不能利用多核CPU的,
# 如果想利用多核CPU的话,就得使用多进程,python中多进程使用multiprocessing模块

from multiprocessing import Process
import time

def my(name):
    time.sleep(3)
    print('hello',name)

p = Process(target=my,args=('hei',)) #方式与多进程相同
p.start()
p.join

七、进程池

# 进程池用来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数,
# 咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;
# 但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务

from multiprocessing import Pool
import os

def worker(msg):
    print("%s开始执行,进程号为%d" % (msg, os.getpid()))


if __name__ == '__main__':

    po = Pool(3)  # 定义一个进程池,最大进程数3
    for i in range(0, 10):
        # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(func=worker, args=(i,))
    # 第一个func参数指定运行的函数,第二个args是参数,没有参数可以不写
    print("----start----")
    po.close()  # 关闭进程池,关闭后po不再接收新的请求
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")
原文地址:https://www.cnblogs.com/ddxxn/p/11476984.html