一、线程
有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。是被系统独立调度和和分派的基本单位。线程不独立拥有系统资源,但是它可以与同属一个进程的其他线程共享该进程资源。同一个进程的多线程之间可以并发执行。
线程也有就绪、阻塞和运行三种基本状态。
就绪状态是指线程具备运行的条件,逻辑上可以运行,在等待处理机;
运行状态是指线程占有处理机正在运行;
阻塞状态是指线程在等待一个事件(如信号量),逻辑上不可执行。
每一个应用程序中都至少一个线程和一个进程。在单个程序中同时运行多个线程完成不同的工作,叫做多线程。
普通的多线程:
import threading
import time
def foo(num):
time.sleep(1)
print("now is number {}".format(num))
for i in range(10):
t = threading.Thread(target=foo, args=(i,))
t.start()
print("main thread")
----------------------------------------------------------------------------
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程的名称
setDaemon 设置为守护线程。即主线程是否等待子线程执行完毕。
join 让程序变为串行的
自定义线程类:
import threading
class MyThreading(threading.Thread):
def __init__(self, *args, **kwargs):
super(MyThreading, self).__init__(*args, **kwargs)
def run(self):
print("now is number {}".format(self.num))
for i in range(10):
t = MyThreading(num=i)
t.start()
锁的概念是:限制某一时刻只有一个线程能访问某个指定的数据
锁一共有五种:
1.Lock 普通锁,不支持嵌套
2.RLock 普通锁,支持嵌套
3.BoundedSemaphore 信号量
4.Event 事件
5.Condition 条件
1. 普通锁
普通锁也叫互斥锁,是独占的,同一时刻只有一个线程被执行。
包括Lock和RLock
import time
import threading
num = 10
def foo(lock):
global num
lock.acquire()
num -= 1
time.sleep(1)
print(num)
lock.release()
lock = threading.RLock()
for i in range(10):
t = threading.Thread(target=foo, args=(lock,))
t.start()
2. 信号量
类名:BoundedSemaphore
这种锁允许一定数量的线程同时更改数据,他不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。
import threading
import time
def foo(i, semaphore):
semaphore.acquire() # 上锁
print(i)
time.sleep(1)
semaphore.release() #释放锁
semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时执行
for i in range(10):
t = threading.Thread(target=foo, args=(i, semaphore))
t.start()
3. 事件
类名:Event
主要提供了三个方法,
event = threading.Event()
event.set()
event.clear()
event.wait()
事件机制:全局定义了一个Flag,如果Flag为False的话,那么当程序执行wait时就会阻塞,如果Flag为True,执行wait时就不再阻塞。类似交通红绿灯
event.clear() ---将Flag设置False
event.set() ---将Flag设置True
import threading
import time
'''
线程的event
'''
event = threading.Event()
def lighter():
counter = 0
event.set() # 设置标志位,表示现在是绿灯,可以通行
while True:
if counter >= 5 and counter < 10:
event.clear() #清除标志位,表示红灯亮起,禁止通行
print("\033[41;1m now is red light,please waiting......\033[0m")
elif counter > 10:
event.set() # 重设标志位,并且使计数器重设成0
counter = 0
else:
print("\033[42;1m now is green light,start running......\033[0m")
time.sleep(1)
counter += 1
def car(name):
while True:
if event.is_set():
print("[%s] is running" % name)
time.sleep(1)
else:
print("now ,Please waiting to green light [%s]" % name)
event.wait()
print("becoming green,you can running [%s]" % name)
light = threading.Thread(target=lighter,)
light.start()
cars = threading.Thread(target=car, args=("Tesla",))
cars.start()
4. 条件
类名:Condition
该机制会使线程等待,只有满足某条件时,才释放n个线程
import threading, time
def run(n):
con.acquire()
con.wait()
print("run the thread: %s" %n)
con.release()
if __name__ == '__main__':
con = threading.Condition()
for i in range(10):
t = threading.Thread(target=run, args=(i,))
t.start()
while True:
inp = input('>>>')
if inp == "q":
break
# 下面这三行是固定语法
con.acquire()
con.notify(int(inp)) # 这个方法接收一个整数,表示让多少个线程通过
con.release()
time.sleep(0.5)
二、队列
队列是一种先进先出的数据结构,与之对应的是堆栈这种后进先出的结构。python内置了一个queue模块,包含队列如下:
1.queue.Queue(): 先进先出队列
2.queue.LifoQueue(): 后进先出队列
3.queue.PriorityQueue(): 优先级队列
4.queue.deque(): 双向队列
(1)先进先出队列--queue.Queue(5)
import queue
q = queue.Queue(5) # 5是指定队列的大小,为空的话队列无限大
for i in range(5):
q.put(i)
print(q.get())
队列的一些方法:
q.qsize() ---获取当前队列中元素的个数,也就是当前队列的大小
q.empty() ---判断当前队列是否为空,返回True或False
q.full() ---判断当前队列是否已满,返回True或False
q.put(self, block=False, timeout=None) ---往队列里放入一个元素,默认是不阻塞和无时间限制的。
q.get(self, block=False, timeout=None) ---在队列里取一个元素,参数和放入是一样的。
q.join() ---阻塞进程,直到所有任务完成,需配合task_done方法使用。
q.task_done() ---表示某个任务完成。每一条get语句后需要一条task_done
(2)后进先出队列--queue.LifoQueue()
import queue
q = queue.LifoQueue()
q.put(123)
q.put(456)
print(q.get())
>>>>>>>>>>>>>>获得456
(3)优先级队列--queue.PriorityQueue()
带有权重的队列,每个元素都是一个元组,前面的数字表示它的优先级,数字越小优先级越高,同样的优先级先进先出。
import queue
q = queue.PriorityQueue()
q.put((1, 'alex'))
q.put((12, 'alice'))
q.put((1, 'jack'))
q.put((13, 'eric'))
print(q.get())
print(q.get())
print(q.get())
>>>>结果是(1, 'alex') (1, 'jack') (12, 'alice')
获取其中的元素就是和列表一样,用索引来获取
(4)双向队列--queue.deque()
import queue
q = queue.deque()
q.append(123)
q.append(333)
q.appendleft(456)
q.pop()
q.popleft()
三、进程
在python中multiprocessing模块提供了Process类,实现进程相关的功能。但是他是基于fork机制的,因此不被windows平台支持,想要在windows中运行,必须使用if __name__ == '__main__:的方式,显然这只能用于调试和学习,不能用于实际环境。
(PS:在这里我必须吐槽一下python的包、模块和类的组织结构。在multiprocess中你既可以import大写的Process,也可以import小写的process,这两者是完全不同的东西。这种情况在python中很多,新手容易傻傻分不清。)
一个简单的进程的栗子:
from multiprocessing import Process
def foo(i):
print("This is Process ", i)
if __name__ == '__main__':
for i in range(5):
p = Process(target=foo, args=(i,))
p.start()
用法和线程其实差不多。
(3.1)进程的数据共享
from multiprocessing import Process
temp_list = []
def foo(i):
temp_list.append(i)
print("This is Process {},list is :{}".format(i, temp_list))
if __name__ == '__main__':
for i in range(5):
p = Process(target=foo, args=(i,))
p.start()
结果是:
可以发现进程间的数据是相互独立的,在各个进程中只有自己的数据,完全不发共享。若要进程间共享资源可以用queues/Array/Manager这三个multiprocess模块提供的类。
(3.2)使用Array共享数据
from multiprocessing import Process
from multiprocessing import Array
def Foo(i,temp):
temp[0] += 100
for item in temp:
print(i,'----->',item)
if __name__ == '__main__':
temp = Array('i', [11, 22, 33, 44])
for i in range(2):
p = Process(target=Foo, args=(i,temp))
p.start()
每一次的数据都是在上一次的基础上进行叠加
(3.3)使用Manager共享数据
from multiprocessing import Process,Manager
def Foo(i,dic):
dic[i] = 100+i
print(dic.values())
if __name__ == '__main__':
manage = Manager()
dic = manage.dict()
for i in range(10):
p = Process(target=Foo, args=(i,dic))
p.start()
p.join()
Manager比Array要好用一点,因为它可以同时保存多种类型的数据格式
四、协程
线程和进程的操作是由程序触发系统接口,最后的执行者是系统,它本质上是操作系统提供的功能。
而协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)
栗子:
from gevent import monkey;
import gevent
import requests
monkey.patch_all()
def f(url):
print('GET: %s' % url)
resp = requests.get(url)
data = resp.text
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
进程、线程和协程的区别
python中进程和线程的最大区别在于稳定性和效率问题。
多进程之间互不影响,一个崩溃了不影响其他进程,稳定性较高
多线程由于在同一个进程里,一个线程崩溃整个进程也会崩溃。
多进程对系统资源开销大,多线程对系统资源开销小,这方面多线程占一点优势。
协程最大的优势是高执行效率,因为子程序切换不是线程切换,没有切换线程的开销,不需要多线程锁。
区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。