并发编程(一) 线程
一 进程与线程
进程
最小的资源管理单位(盛放线程的容器),进程之间的数据集是相互独立的
进程组成:
1.程序
2.数据集:程序执行过程中多需要使用的资源
3.进程控制块:保存进程状态
线程
最小的执行单位(容器里的东西),线程又叫做微进程,一个进程包括多个线程,共享同一个数据集
线程组成:
1.线程ID
2.程序计数器
3.寄存器集合
4.堆栈
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
进程与线程的关系
进程和线程的关系:
1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
2.资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3.CPU分给线程,即真正在CPU上运行的是线程。
并发和并行
并发:能同时解决多个问题的能力,一个cpu通过切换进程实现并发
并行:一个cpu执行一个进程,多个cpu各种执行自己的进程
操作系统并发情况下,什么时候切换进程:
1.出现IO操作
2.固定时间
二 Python线程
python中的threading模块,提供了线程相关操作
2.1 Thread类对象创建
直接创建
import threading
import time
def clock(n): # 定义某个线程要运行的函数
print('{0}\'s clock begin!'.format(n))
time.sleep(n)
print('{0}\'s clock time\'s up!'.format(n))
print('main threading!')
t1 = threading.Thread(target=clock, args=(10,)) # 生成一个线程实例
t2 = threading.Thread(target=clock, args=(15,))
t1.start() # 启动线程
t2.start()
print('main threading end!')
结果:
main threading!
10's clock begin!
15's clock begin!
main threading end!
... 10秒后
10's clock time's up!
... 5秒后
15's clock time's up!
继承创建
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self):
print('{0}\'s clock begin!'.format(self.num))
time.sleep(self.num)
print('{0}\'s clock time\'s up!'.format(self.num))
print('main threading!')
t1 = MyThread(10)
t2 = MyThread(15)
t1.start()
t2.start()
print('main threading end!')
2.2 Thread类方法
基本方法
start()
启动线程,等待CPU调度join()
阻塞主线程,等待子线程结束,再执行主线程(在子线程完成运行之前,这个子线程的父线程将一直被阻塞)setDaemon(True)
将子线程设置为守护线程,当主线程结束时,子线程也结束,主要用途:作为监听功能使用,在start()
方法之前使用run()
线程被cpu调度后自动执行线程对象的run方法
import threading
import time
def power(n):
print('游戏{}秒钟'.format(n))
time.sleep(n)
print('{}秒钟游戏结束'.format(n))
def call(n):
print('通话{}秒钟'.format(n))
time.sleep(n)
print('{}秒钟通话结束'.format(n))
s = time.time()
print('开机!')
t1 = threading.Thread(target=power, args=(5,))
t2 = threading.Thread(target=call, args=(2,))
# ==== 并发 ====
t1.start()
t2.start()
t1.join() # 线程t1,阻塞主线程
t2.join()
# ==== 串行 ====
# t1.start()
# t1.join
# t2.start()
# t2.join()
print('关机!')
print('手机运行{}秒'.format(time.time() - s))
ps:
利用调整join()
方法的位置,我们可以控制子线程们是串行或并发:
- 串行:join函数的作用是等待当前线程结束,所以每一个线程创建之后,调用
start()
,这时紧跟该线程的join()
,那么就是顺序执行; - 并发:如果多个线程先完成示例化和
start()
,最后加上统一调用join()
,那么就变成了并发。
其他方法
除上述,Thread类提供的另一些方法
isAlive()
返回线程是否存活getName()
返回线程名setName()
设置线程名称
threading模块提供的一些方法:
threading.currentThread()
返回当前的线程变量threading.enumerate()
返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程threading.activeCount()
返回正在运行的线程数量,与len(threading.enumerate())
有相同的结果
2.3 GIL(全局解析器锁)
定义
GIL(global interpreter lock):在CPython中,全局解释器锁是一种互斥锁,防止多个本地线程执行Python字节码。 这个锁是必要的,因为CPython的内存管理不是线程安全的。 (但是,由于GIL存在,其他功能已经发展到依赖于它实施的保证。)
ps:GIL是加在cpython解释器上的,jpython/pypy等都没有GIL
影响
由于GIL,导致同一时刻,同一进程,只能有一个线程被执行
ps:
对于计算密集型任务:多个线程的代码很有可能是线性执行的(省掉上下文切换)
对IO密集型任务:多线程可以明显提高效率
如何避免受到GIL的影响?
用multiprocess替代Thread(multiprocess进程)
multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。
multiprocess的本质就是开多进程,但是缺陷时开销大,切换复杂
其他解决方法还有:
- 协程 + 多进程,其本质就是单线程 + 多进程
- IO多路复用
- 换C模块实现多线程
2.4 线程锁
由于操作系统是随机调度线程的执行的,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,保证同一时刻允许一个线程执行操作。
同步锁(Lock)
多个线程对共享资源访问时用到同步锁,线程用到资源进行acquire(上锁),资源访问完毕release(解锁)
使用方法:
l = threading.Lock() # 同步锁
l.acquire() # 上锁
# 上锁的代码
l.release() # 解锁
只能有一个线程拿到上锁acquire资源,上锁资源被使用完毕后,被release,下一个线程才能拿到上锁资源,其他资源不受锁的影响
import time
import threading
l = threading.Lock()
def subNum():
global num
# num -= 1
l.acquire() # 上锁
temp = num
time.sleep(0.01)
num = temp - 1
l.release() # 解锁
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=subNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('result:', num)
递归锁(RLock)
解决死锁问题
使用方法:
rl = threading.RLock() # 递归锁
rl.acquire() # 上锁 计数+1 counter=1
rl.acquire() # 上锁 计数+1 counter=2
...
rl.release() # 解锁 计数-1 counter=1
rl.release() # 解锁 计数-1 counter=0
counter记录了acquire的次数,直到一个线程所有的acquire都被release,即count为0时,其他线程才可以访问该资源
import threading
import time
Rlock = threading.RLock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.func1()
self.func2()
def func1(self):
Rlock.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 counter = 1
print('I am %s ,get res: %s --- %s ' % (self.name, 'ResA', time.time()))
Rlock.acquire() # counter = 2
print('I am %s ,get res: %s --- %s ' % (self.name, 'ResB', time.time()))
Rlock.release() # counter = 1
Rlock.release() # counter = 0
def func2(self):
Rlock.acquire() # counter = 1
print('I am %s ,get res: %s --- %s ' % (self.name, 'ResB', time.time()))
time.sleep(0.2)
Rlock.acquire() # counter = 2
print('I am %s ,get res: %s --- %s ' % (self.name, 'ResA', time.time()))
Rlock.release() # counter = 1
Rlock.release() # counter = 0
if __name__ == '__main__':
print('start ----------- %s'%time.time())
for i in range(0,10):
mt = MyThread()
mt.start()
2.5 事件(Event)
线程之间的通信作用
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
r = threading.Event() # 相当于设置标志位flag = False
event.wait() # 阻塞,等待falg状态,可以加参数,表示等待秒数
r.set() # 将 flag = True
event.isSet() # 判断False或True
import logging
import time
import threading
logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', )
def worker(event):
logging.debug('等待redis准备…')
while not event.isSet():
logging.debug('等待连接...')
event.wait(3) # if flag = False阻塞,等待flag = True 继续执行
logging.debug('redis准备好,并连接到redis服务器和做一些工作 %s', time.ctime())
time.sleep(1)
def main():
r = threading.Event() # flag = False
t1 = threading.Thread(target=worker, args=(r,), name='t1')
t1.start()
t2 = threading.Thread(target=worker, args=(r,), name='t2')
t2.start()
logging.debug('首先,检查redis服务器,确保它是OK,然后触发复述事件做好准备')
time.sleep(6)
r.set() # flag = True
if __name__ == '__main__':
main()
2.6 信号量(Semaphore)
互斥锁 同一时刻只能有一个线程更改数据
同时只有n个线程可以获得semaphore,即可以限制最大连接数为n)
使用方法:
s = threading.Semaphore(n) # n个连接数
s.acquire()
...
s.release()
import threading
import time
s = threading.Semaphore(5) #同时5个线程获取semaphore
def foo():
s.acquire()
print('%s 获到了semaphore' % threading.current_thread().getName())
time.sleep(2)
s.release()
for i in range(20):
t = threading.Thread(target=foo)
t.start()