python下的多线程与多进程

多进程:

进程我们可以理解为是一个可以独立运行的程序单位,比如打开一个浏览器,这就开启了一个浏览器进程;打开一个文本编辑器,这就开启了一个文本编辑器进程。但一个进程中是可以同时处理很多事情的,比如在浏览器中,我们可以在多个选项卡中打开多个页面,有的页面在播放音乐,有的页面在播放视频,有的网页在播放动画,它们可以同时运行,互不干扰。为什么能同时做到同时运行这么多的任务呢?这里就需要引出线程的概念了,其实这一个个任务,实际上就对应着一个个线程的执行。

而进程呢?它就是线程的集合,进程就是由一个或多个线程构成的,线程是操作系统进行运算调度的最小单位,是进程中的一个最小运行单元。比如上面所说的浏览器进程,其中的播放音乐就是一个线程,播放视频也是一个线程,当然其中还有很多其他的线程在同时运行,这些线程的并发或并行执行最后使得整个浏览器可以同时运行这么多的任务。

并行与并发:

并发,英文叫作 concurrency。它是指同一时刻只能有一条指令执行,但是多个线程的对应的指令被快速轮换地执行。比如一个处理器,它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。

由于处理器执行指令的速度和切换的速度非常非常快,人完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作,这就使得宏观上看起来多个线程在同时运行。但微观上只是这个处理器在连续不断地在多个线程之间切换和执行,每个线程的执行一定会占用这个处理器一个时间片段,同一时刻,其实只有一个线程在执行。

并行,英文叫作 parallel。它是指同一时刻,有多条指令在多个处理器上同时执行,并行必须要依赖于多个处理器。不论是从宏观上还是微观上,多个线程都是在同一时刻一起执行的。

并行只能在多处理器系统中存在,如果我们的计算机处理器只有一个核,那就不可能实现并行。而并发在单处理器和多处理器系统中都是可以存在的,因为仅靠一个核,就可以实现并发。

举个例子,比如系统处理器需要同时运行多个线程。如果系统处理器只有一个核,那它只能通过并发的方式来运行这些线程。如果系统处理器有多个核,当一个核在执行一个线程时,另一个核可以执行另一个线程,这样这两个线程就实现了并行执行,当然其他的线程也可能和另外的线程处在同一个核上执行,它们之间就是并发执行。具体的执行方式,就取决于操作系统的调度了。

多线程使用场景:

在一个程序进程中,有一些操作是比较耗时或者需要等待的,比如等待数据库的查询结果的返回,等待网页结果的响应。如果使用单线程,处理器必须要等到这些操作完成之后才能继续往下执行其他操作,而这个线程在等待的过程中,处理器明显是可以来执行其他的操作的。如果使用多线程,处理器就可以在某个线程等待的时候,去执行其他的线程,从而从整体上提高执行效率。

像上述场景,线程在执行过程中很多情况下是需要等待的。比如网络爬虫就是一个非常典型的例子,爬虫在向服务器发起请求之后,有一段时间必须要等待服务器的响应返回,这种任务就属于 IO 密集型任务。对于这种任务,如果我们启用多线程,处理器就可以在某个线程等待的过程中去处理其他的任务,从而提高整体的爬取效率。

但并不是所有的任务都是 IO 密集型任务,还有一种任务叫作计算密集型任务,也可以称之为 CPU 密集型任务。顾名思义,就是任务的运行一直需要处理器的参与。此时如果我们开启了多线程,一个处理器从一个计算密集型任务切换到切换到另一个计算密集型任务上去,处理器依然不会停下来,始终会忙于计算,这样并不会节省总体的时间,因为需要处理的任务的计算总量是不变的。如果线程数目过多,反而还会在线程切换的过程中多耗费一些时间,整体效率会变低。

所以,如果任务不全是计算密集型任务,我们可以使用多线程来提高程序整体的执行效率。尤其对于网络爬虫这种 IO 密集型任务来说,使用多线程会大大提高程序整体的爬取效率。

多线程示例:

1.基本使用

import threading
import time

def func1(n):
    print(f‘{threading.current_thread().name} is running‘)
    print(f‘{threading.current_thread().name} is sleep {n}s‘)
    time.sleep(n)
    print(f‘{threading.current_thread().name} is end‘)


for i in [1, 5]:
    t = threading.Thread(target=func1, args=[I])  # 创建一个线程 第一个参数为调用的方法,第二个是传递的参数(以列表的方式)
    t.start() #开始线程任务  #t.join() 

print(f‘{threading.current_thread().name} is ended‘)

特点:
    主线程与子线程是各自跑自己的程序,也就是说主线程结束了,可能子线程还在运行当中如果我想让主线程等待子线程执行完成之后,再向下执行,就需要添加一个join方法

2.支持使用继承线程类的方式

import threading

class MyThread(threading.Thread):

    def __init__(self, second):
        threading.Thread.__init__(self) # 继承父类初始化方法
        self.second = second

    def run(self):
        print(f‘Threading {threading.current_thread().name} is running‘)
        print(f‘Threading {threading.current_thread().name} is sleep {self.second}s‘)
        print(f‘Threading {threading.current_thread().name} is end‘)

print(f‘Threading {threading.current_thread().name} is running...‘)

for i in [1, 5]:
    t = MyThread(i)
    t.start()
    # t.join()

print(f‘Threading {threading.current_thread().name} is ended‘)

3. 在线程中有一个叫作守护线程的概念,如果一个线程被设置为守护线程,那么意味着这个线程是“不重要”的,这意味着,如果主线程结束了而该守护线程还没有运行完,那么它将会被强制结束。

import threading
import time

def func1(n):
    print(f‘{threading.current_thread().name} is running‘)
    print(f‘{threading.current_thread().name} is sleep {n}s‘)
    time.sleep(n)
    print(f‘{threading.current_thread().name} is end‘)

print(f‘Threading {threading.current_thread().name} is running‘)
# 使用守护线程方式运行
t1 = threading.Thread(target=func1, args=[1])
t1.start()
t1.join()

t2 = threading.Thread(target=func1, args=[5])
t2.setDaemon(True)
t2.start()
#t2.join()
print(f‘Threading {threading.current_thread().name} is ended‘)#这样主线程结束之后,子线程即使没有执行完成,也会强制退出, 如果不想这样的话,可以添加join方法等待子进程的结束

4. 互斥锁(一个进程中,多个线程之间是资源共享的)

import threading
import time

count = 0
class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global count
        #lock.acquire()
        temp = count+1
        time.sleep(0.001)
        count = temp
        #lock.release() # 释放锁


#lock = threading.Lock()

threads = []
for i in range(1, 1001):
    print(i)
    t = MyThread()
    t.start()
    threads.append(t)

for thread in threads:
    thread.join()

print(f‘Final count: {count}‘)


没有加锁的情况下,最终得到的count并不是1000, 而是比1000较小,

为了避免,我们需要对多个线程进行同步,要实现同步,我们可以对需要操作的数据进行加锁保护 使用线程锁 threading.Lock()

经历了获取锁==>lock.acquire()
执行逻辑代码
释放锁==> lock.release()