目录
1. 线程与进程
1.1 线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程执行不同的任务。
- 线程是独立调度和分派的基本单位。
- 线程就是一堆指令集,100条字节码
1.2 进程
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
1.3 并发和并行
- 并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
- 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 多线程就是两个线程并行的执行。
- 线程间可以共享数据,进程不可以。
1.4 线程和进程的区别
- 同一进程的线程共享本进程的地址空间,而进程间则是独立的共享空间。
- 同一进程内的线程可以共享本进程的资源如内存、I/O、CPU等,而进程间的资源是独立的。
- 线程间可以通信,进程间不可以。
- 主线程可以影响子线程,但主进程不能影响子进程。
- 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
1.5 CPU发生切换的情况
- 多个线程在执行
- 遇到IO阻塞(socket的accept、recv,文件操作的read),都有一个等待的过程。sleep就相当于IO阻塞
2. threading模块
2.1 threading模块常用的函数及方法
函数 | 描述 |
Thread() | 创建一个线程对象 |
current_thread() | 获取当前线程的名字 |
active_count() | 获取当前正在运行的线程的数量 |
enumerate() | 返回一个包含正在运行的线程的列表。正在运行指线程启动后、结束前,不包括启动前和终止后的线程 |
实例化对象的方法 | 描述 |
t.start() | 线程的执行 |
t.setDaemon() | 守护线程 |
t.join() | 等待至线程中止。阻塞调用线程至线程的join()方法被调用中止-正常退出或者抛出未处理的异常--或者是可选的超时时发生 |
2.2 线程的创建
(1)直接创建
import time
import threading
begin = time.time()
def foo(n):
print("foo%s"%n)
time.sleep(1) #sleep时候不占用CPU
print("end foo")
def bar(n):
print("bar%s"%n)
time.sleep(2)
print("end bar")
t1 = threading.Thread(target=foo, args=(1,)) #线程的创建
t1.start() #线程的执行
t2 = threading.Thread(target=bar, args=(2,))
t2.start()
print(".......in the main...........")
t1.join()
t2.join()
end = time.time()
print(end-begin)
(2)继承式调用
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self): #定义每个线程要运行的函数
print("running on number:%s" % self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1) #实例化对象
t2 = MyThread(2)
t1.start()
t2.start()
2.3 join()方法
阻塞主线程,只有当该子线程完成后主线程才能执行
import time, threading
begin = time.time()
def add(n):
sum = 0
for i in range(n):
sum += i
print(sum)
# add(100000000)
# add(100000000)
t1 = threading.Thread(target=add, args=(100000000,))
t1.start()
t2 = threading.Thread(target=add, args=(100000000,))
t2.start()
t1.join()
t2.join()
end = time.time()
print(end-begin)
在2.7和3.4以下串行比并行快近一倍,3.5以优化,切换的时候消耗很大时间/
2.4 GIL全局解释器锁
CPython解释器有GIL。在同一时刻,一个进程只能有一个线程进入CPython解释器。如果要使用上多核的话,应使用多进程。
计算密集型和IO密集型
IO密集型有阻塞状态,阻塞状态少的时候为计算密集型
在Python中,如果处理的任务为IO密集型,可以用多线程;如果为计算密集型,改C。
多线程的实现:协程(不抢占)+多进程;
2.5 setDaemon()函数
- 就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。
- 也就是说:主线程不等待该守护线程的执行完再去关闭
import threading
import time
def music(func):
for i in range(2):
print("Begin I was listening to %s. %s" %(func, time.ctime()))
time.sleep(2)
print("End Listening %s" %(time.ctime()))
def move(func):
for i in range(2):
print("Begin I was at the %s! %s" %(func, time.ctime()))
time.sleep(5)
print("End watching %s" %time.ctime())
if __name__ == '__main__':
t1 = threading.Thread(target=music, args=("七里香",))
t2 = threading.Thread(target=move, args=("阿甘正传",))
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
# t1.join()
t2.start()
print("all over %s" %time.ctime())
2.6 线程安全
import time
import threading
def addNum():
global num
# num -= 1
temp = num
time.sleep(0.1)
num = temp - 1
start = time.time()
num = 100
thread_list = []
for i in range(num):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
t.join()
print("finall num:", num)
end = time.time()
print(end-start)
sleep(0.1),现象会很明显,100个线程每一个一定都没有执行完就进行了切换,sleep相当于IO阻塞,0.1s内不会再切换回来,所以100个线程拿到的num都为99.
若使用join,会把整个线程给挺住,造成了串行,失去了多线程的意义,而我们只需要把计算(涉及到操作公共数据)的时候串行执行。
2.7 同步锁Lock
可以通过同步锁来解决这种问题,加锁的意义在于防止冲突。
import time
import threading
def addNum():
global num
# num -= 1
r.acquire() #获取锁
temp = num
time.sleep(0.1)
num = temp - 1
r.release() #释放锁
start = time.time()
num = 100
thread_list = []
r = threading.Lock() #
for i in range(num):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print("finall num:", num)
end = time.time()
print(end-start)
2.8 同步锁与GIL的关系
Python的线程在GIL的控制之下,线程之间,对整个Python解释器,对Python提供的C API的访问都是互斥的,这可以看作是对Python内核级的互斥机制。但这种互斥不是我们所能控制的,还需要另外一种可控的互斥机制---用户级互斥。内核级通过互斥保护了内核的共享资源,同样,用户级互斥保护了用于程序中的共享资源。
GIL的作用是,对一个解释器,只能有一个thread在执行bytecode。所以每时每刻只有一条bytecode在被执行一个thread。GIL保证了bytecode这层面上是thread safe的。
但是如果你有个操作比如x += 1,这个操作需要多个bytecode操作,在执行这个操作的多条bytecodes期间的时候可能中途就换thread了,这样就出现了data races的情况了。
2.9 线程死锁
在线程间共享多个资源的时候,如果两个thread分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所以这两个线程在无外力作用下将一直等待下去。
import threading,time
class myThread(threading.Thread):
def doA(self):
lockA.acquire()
print(self.name,'gotlockA', time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name, 'gotlockB', time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB.acquire()
print(self.name, 'gotlockB', time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name, 'gotlockA', time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__ == '__main__':
lockA = threading.Lock()
lockB = threading.Lock()
threads = []
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
2.10 递归锁
import threading,time
class myThread(threading.Thread):
def doA(self):
lock.acquire()
print(self.name,'gotlockA', time.ctime())
time.sleep(3)
lock.acquire()
print(self.name, 'gotlockB', time.ctime())
lock.release()
lock.release()
def doB(self):
lock.acquire()
print(self.name, 'gotlockB', time.ctime())
time.sleep(2)
lock.acquire()
print(self.name, 'gotlockA', time.ctime())
lock.release()
lock.release()
def run(self):
self.doA()
self.doB()
if __name__ == '__main__':
# lockA = threading.Lock()
# lockB = threading.Lock()
lock = threading.RLock()
threads = []
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
2.11 信号量(并行锁)
信号量是用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1.
计时器不能小于0,当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
import threading
import time
class myThread(threading.Thread):
def run(self):
semaphore.acquire() #加锁
print(self.name, time.ctime())
time.sleep(3)
semaphore.release() #释放锁
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) #5为控制的线程个数
thrs = []
for i in range(8):
thrs.append(myThread())
for t in thrs:
t.start()
2.12 条件变量同步
可以实现线程间的通信
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition对象用于条件变量线程的支持,它除了能提供Rlock()或Lock()的方法外,还提供了wait(),notify(),notifyAll()方法。
lock_on = threading.Condition([Lock/RLock]):锁是可选项,不传人锁,对象自动创建一个Rlock().
- wait():条件不满足时调用,线程会释放锁并进入等待阻塞
- notify():条件创造后调用,通知等待池激活一个线程
- notifyAll():条件创建后调用,通知等待池激活所有线程
import threading,time
from random import randint
class Producer(threading.Thread):
def run(self):
global L
while True:
val = randint(0, 100)
print("生产者", self.name, 'Append' + str(val), L)
#获取锁,用于线程同步
if lock_con.acquire():
L.append(val)
lock_con.notify()
#释放锁,开启下一个线程
lock_con.release()
time.sleep(3)
class Consumer(threading.Thread):
def run(self):
global L
while True:
lock_con.acquire()
if len(L) == 0:
lock_con.wait()
print("消费者", self.name, "Delete" + str(L[0]), L)
del L[0]
lock_con.release()
time.sleep(1)
if __name__ == "__main__":
L = []
lock_con = threading.Condition()
threads = []
for i in range(5):
threads.append(Producer())
threads.append(Consumer())
for t in threads:
t.start()
for t in threads:
t.join()
print("-------------end-------------------")
2.13 同步条件event
条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event = threading.Event():条件环境对象,初始值为False
- event.isSet():返回event的状态值
- event.wait():如果为False将阻塞线程
- event.set():设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度
- event.clear():恢复event的状态值为False
import threading, time
class Boss(threading.Thread):
def run(self):
print("BOSS:今晚大家都要加班到22:00.", time.ctime())
event.isSet() or event.set()
time.sleep(5)
print("BOSS:22:00可以下班了.", time.ctime())
event.isSet() or event.set()
class Worker(threading.Thread):
def run(self):
event.wait()
print("Worker:哎呀-----命苦呀!", time.ctime())
time.sleep(1)
event.clear()
event.wait()
print("Worker:OhYeah!", time.ctime())
if __name__ == '__main__':
event = threading.Event()
threads = []
for i in range(3):
threads.append(Worker())
threads.append(Boss())
for t in threads:
t.start()
for t in threads:
t.join()
2.14 多线程利器(queue)
队列里本身就有一把锁
- Queue.qsize() 返回队列的大小
- Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False
- Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait() 相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- Queue.join() 实际上意味着等到队列为空,再执行别的操作
import queue,threading
from random import randint
import time
class Production(threading.Thread):
def run(self):
while True:
r = randint(0, 100)
q.put(r)
print("生产出来%s号包子"%r)
time.sleep(1)
class Proces(threading.Thread):
def run(self):
while True:
re = q.get()
print("吃掉%s号包子"%re)
if __name__ == '__main__':
q = queue.Queue()
threads = [Production(), Production(), Production(), Proces()]
for t in threads:
t.start()