进程 VS 线程
一、进程和线程的区别
1、进程是对运行时程序的封装,是系统资源调度和分配的基本单位。
2、线程是进程的子任务,CPU 调度和分配的基本单位,实现进程内并发。
3、一个进程可以包含多个线程,线程依赖进程存在,并共享进程内存。
二、线程安全
1、python 哪些操作是线程安全的?
(1) 一个操作可以在多线程环境中安全使用,获取正确的结果。
(2) 线程安全的操作好比线程是顺序执行而不是并发执行的(i += 1)。
(3) 一般如果涉及到写操作需要考虑如何让多个线程安全访问数据。
三、线程同步的方式
了解线程同步的方式,如何保证线程安全。
1、互斥锁(Lock):通过互斥机制防止多个线程同时访问公共资源。
由于线程之间是进行随机调度,并且每个线程可能只执行 n 条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据。所以,出现了线程锁,即同一时刻只允许一个线程执行操作。线程锁用于锁定资源,我们可以定义多个锁,像下面的代码,当我们需要独占某一资源时,任何一个锁都可以锁定这个资源,就好比我们用不同的锁都可以把相同的一个门锁住是一个道理。
由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为 "线程不安全"。
为了防止上面情况的发生,就出现了互斥锁(Lock)。
from threading import Thread, Lock
import time
class MyThread(Thread):
def __init__(self, threadID, name, counter):
Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
global num
print("开启线程:" + self.name)
# 获得锁,用于线程同步
threadLock.acquire()
num += 1
print_time(self.name, self.counter, 3)
print("num: ", num)
# 释放锁,开启下一个线程
threadLock.release()
print("退出线程:" + self.name)
def print_time(thredName, delay, counter):
while counter:
time.sleep(delay)
print("%s: %s" % (thredName, time.ctime(time.time())))
counter -= 1
if __name__ == "__main__":
threadLock = Lock()
thread_list = []
num = 0
# 创建线程
# thread1 = MyThread(1, "Thread-1", 1)
# thread2 = MyThread(2, "thread-2", 2)
# 开启新线程
# thread1.start()
# thread2.start()
# 添加线程到线程列表
# thread_list.append(thread1)
# thread_list.append(thread2)
for i in range(10):
thread = MyThread(i, "Thread-" + str(i), i)
thread.start()
thread_list.append(thread)
# 等待所有线程完成
for t in thread_list:
t.join()
print("退出主线程")
2、递归锁(RLock):
RLock 类的用法和 Lock 类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLock类。
(1) 加锁前:
from threading import Thread, RLock
import time
def Func(lock):
global num
# lock.acquire()
num += 1
time.sleep(1)
print(num)
# lock.release()
if __name__ == "__main__":
num = 0
lock = RLock()
for i in range(10):
thread = Thread(target=Func, args=(lock,))
thread.start()
# thread.join()
# 运行结果:
10
10
10
10
10
10
10
10
10
10
(2) 加锁后:
from threading import Thread, RLock
import time
def Func(lock):
global num
# lock.acquire()
num += 1
time.sleep(1)
print(num)
# lock.release()
if __name__ == "__main__":
num = 0
lock = RLock()
for i in range(10):
thread = Thread(target=Func, args=(lock,))
thread.start()
# thread.join()
# 运行结果:
1
2
3
4
5
6
7
8
9
10
3、信号量(Semphare):控制同一时刻多个线程访问同一资源的线程数。
互斥锁同时只允许一个线程更改数据,而 Semaphore 是同时允许一定数量的线程去更改数据,比如一个餐桌有三个座位,那最多只允许三个人在餐桌座位上就餐,后面的人只能等前面的人就餐结束才能在餐桌座位就餐。
(1) 未加锁:
from threading import Thread, BoundedSemaphore, active_count
import time
def run(n, semaphore):
global num
# semaphore.acquire()
num += 1
time.sleep(1)
print("run the thread: %s\n" % n)
print("num: ", num)
# semaphore.release()
if __name__ == "__main__":
num = 0
semaphore = BoundedSemaphore(3) # 最多允许3个线程同时运行
for i in range(10):
thread = Thread(target=run, args=("thread-%s" %i, semaphore))
thread.start()
while active_count() != 1: # 返回当前处于激活状态的Thread对象个数,返回的个数等于threading.enumerate()返回的list的长度
pass
else:
print("-------- all threads done --------")
# 运行结果:
run the thread: thread-7
run the thread: thread-6
num: 10
run the thread: thread-8
num: 10
run the thread: thread-3
num: 10
run the thread: thread-4
num: 10
run the thread: thread-0
run the thread: thread-9
num: 10
run the thread: thread-1
num: 10
run the thread: thread-2
run the thread: thread-5
num: 10
num: 10
num: 10
num: 10
-------- all threads done --------
(2) 加锁后:
from threading import Thread, BoundedSemaphore, active_count
import time
def run(n, semaphore):
global num
semaphore.acquire()
num += 1
time.sleep(1)
print("run the thread: %s\n" % n)
print("num: ", num)
semaphore.release()
if __name__ == "__main__":
num = 0
semaphore = BoundedSemaphore(3) # 最多允许3个线程同时运行
for i in range(10):
thread = Thread(target=run, args=("thread-%s" %i, semaphore))
thread.start()
# thread.join()
while active_count() != 1: # 返回当前处于激活状态的Thread对象个数,返回的个数等于threading.enumerate()返回的list的长度
pass
else:
print("-------- all threads done --------")
# 运行结果:
run the thread: thread-0
num: 3
run the thread: thread-2
num: 4
run the thread: thread-1
num: 4
run the thread: thread-5
run the thread: thread-3
num: 6
run the thread: thread-4
num: 7
num: 7
run the thread: thread-6
run the thread: thread-7
num: 9
run the thread: thread-8
num: 9
num: 10
run the thread: thread-9
num: 10
-------- all threads done --------
4、事件(Event):通过通知的方式保持多个线程同步。
事件处理的机制:全局定义了一个 flag,当 flag 值为 False,那么 event.wait() 就会阻塞,当 flag 值为 True,那么 event.wait() 便不再阻塞。
python 线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:
(1) 创建 event 对象。
from threading import Event
event = Event()
(2) event.isSet() 方法,返回event状态值 True 和 False。
from threading import Event
event = Event()
print(event.is_set())
print(event.isSet())
# 运行结果:
False
False
(3) event.wait() 方法,等待的意思,如果你在某一个线程里面调用了 wait,它会判断标志位是 True 还是 False,如果是 True,wait 什么都不做继续往下执行,如果是 False会阻塞,等到标志位被改成 True 才能执行。
from threading import Event
event = Event()
print(event.is_set())
print(event.isSet())
def childtd():
print("wait...")
event.wait() # 如果标志位为 False阻塞,True 执行
print("connetct server")
thread = Thread(target=childtd, args=())
thread.start()
# 运行结果:
False
False
wait...
(4) event.wait(3):等待3秒钟如果标志位还没恢复过来,那就不等了会继续往下执行。它怎么用呢,我们不能在程序3秒后继续往下执行,那就失去了本身的意义,我们可以在等待过程中每2秒打印信息。
from threading import Event
event = Event()
def childtd():
while not event.isSet():
print("等待中...")
event.wait(3) # 如果标志位为 False阻塞,True 执行
print("wait...")
print("connetct server")
thread = Thread(target=childtd, args=())
thread.start()
# 运行结果:
等待中...
等待中...
等待中...
等待中...
等待中...
.........
(5) event.set() 方法,默认标志位是 False,用 set 方法可以设置标志位的值为 True。在主线程修改标志位为 True,让子线程继续往下执行。
from threading import Event
event = Event()
print(event.is_set())
print(event.isSet())
def childtd():
print("wait...")
event.wait() # 如果标志位为 False阻塞,True 执行
print("connetct server")
thread = Thread(target=childtd, args=())
thread.start()
event.set() # 设置标志位值为 True
# 运行结果:
False
False
wait...
connetct server
(6) event.clear() 方法,恢复 event 的状态值为 False。
from threading import Event
event = Event()
print(event.is_set())
event.set()
print(event.is_set())
event.clear()
print(event.isSet())
# 运行结果:
False
True
False
四、进程间通信的方式
Inter-Process Communication 进程间传递信号或者数据。
1、管道/匿名管道/有名管道(pipe)。
2、信号(Signal):比如用户使用 Ctrl + c 产生 SIGINT 程序终止信号。
3、消息队列(Message)。
4、共享内存(share memory)。
5、信号量(Semaphore)。
6、套接字(socket):最常用的方式,我们的 web 应用都是这种方式。
五、python 中如何使用多线程
1、threading 模块,普通创建模式。
(1) threading.Thread 类用来创建线程。
(2) start() 方法启动线程。
(3) 可以用 join() 等待线程结束。
import threading
import time
def Func(n):
print("task ", n)
time.sleep(1)
print("2s")
time.sleep(1)
print("1s")
time.sleep(1)
print("0s")
time.sleep(1)
if __name__ == "__main__":
t1 = threading.Thread(target=Func, args=("t1",))
t2 = threading.Thread(target=Func, args=("t2", ))
t1.start()
t2.start()
# 运行结果:
task t1
task t2
2s
2s
1s
1s
0s
0s
2、自定义线程,继承 threading.Thread 来自定义线程类,其本质是重构 Thread 类中的 run 方法。
from threading import Thread
import time
exitFlag = 0
class myThread(Thread):
def __init__(self, threadID, name, counter):
Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print("开始线程:" + self.name)
print_time(self.name, self.counter, 5)
print("退出线程:" + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print("%s: %s \n" %(threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("退出主线程")
# 运行结果:
开始线程:Thread-1
开始线程:Thread-2
Thread-1: Wed Jun 3 15:20:33 2020
Thread-2: Wed Jun 3 15:20:34 2020
Thread-1: Wed Jun 3 15:20:34 2020
Thread-1: Wed Jun 3 15:20:35 2020
Thread-2: Wed Jun 3 15:20:36 2020
Thread-1: Wed Jun 3 15:20:36 2020
Thread-1: Wed Jun 3 15:20:37 2020
退出线程:Thread-1
Thread-2: Wed Jun 3 15:20:38 2020
Thread-2: Wed Jun 3 15:20:40 2020
Thread-2: Wed Jun 3 15:20:42 2020
退出线程:Thread-2
退出主线程
3、应用场景:一般用在 IO 密集型程序中。
六、python 中如何使用多进程
python 有 GIL,可以用多进程实现 CPU 密集程序。
1、multiprocessing 多进程模块。
2、multiprocessing.Process 类实现多进程。
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
def f(name):
info('function f')
print('hello', name)
if __name__ == '__main__':
info('main line')
p = Process(target=f, args=('bob',))
p.start()
p.join()
# 运行结果:
main line
module name: __main__
parent process: 11508
process id: 12408
function f
module name: __mp_main__
parent process: 12408
process id: 14316
hello bob
3、一般用在 CPU 密集程序中,避免 GIL 的影响。
4、进程池
from multiprocessing import Pool
import time
def fun(x):
return x * x
if __name__ == "__main__":
with Pool(processes=4) as pool: # start 4 worker processes 启动4个辅助进程
# evaluate "f(10)" asynchronously in a single process
# 在单个进程中异步计算 fun(10)
result = pool.apply_async(fun, (10,))
# prints "100" unless your computer is *very* slow
# 打印 100,除非你的电脑非常慢
print(result.get(timeout=1))
# prints "[0, 1, 4,..., 81]"
print(pool.map(fun, range(10)))
it = pool.imap(fun, range(10))
# # prints "0"
print(next(it))
# # prints "1"
print(next(it))
# prints "4" unless your computer is *very* slow
# 打印 4,除非你的电脑非常慢
print(it.next(timeout=1))
result = pool.apply_async(time.sleep, (10,))
# raises multiprocessing.TimeoutError
# get()用于获取执行结果。
# 如果 timeout 不是 None 并且在 timeout 秒内仍然没有执行完得到结果,则抛出 multiprocessing.TimeoutError 异常。
# 如果远程调用发生异常,这个异常会通过 get() 重新抛出。
print(result.get(timeout=1))
pool.close() # 允许pool中的进程关闭(close必须在join前面,可以理解close相当于一个开关吧)
pool.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。