python并发编程之线程
1、为什么要有线程
因为进程虽然有其优点,但也有缺点:
1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
2、线程的出现
正因为进程的缺点,所以需要解决它。在80年代,出现了能独立运行的基本单位——线程(Threads)
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程。
3、进程和线程的区别
可以归纳为以下4点:
1.地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3.调度和切换:线程上下文切换比进程上下文切换要快得多。
4.在多线程操作系统中,进程不是一个可执行的实体。
4、线程的特点
1.轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述,
TCB用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
TCB包括以下信息:
1.线程状态。
2.当线程不运行时,被保存的现场资源。
3.一组执行堆栈。
4.存放每个线程的局部变量主存区。
5.访问同一个进程中的主存和其它资源。
2.独立调度和分派的基本单位
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。
由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3.共享进程资源
线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4.可并发执行
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;
同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
5、如何开启线程
方法一:
from multiprocessing import Process
from threading import Thread
def task():
print("我是子线程")
if __name__ == '__main__':
t = Thread(target=task, args=())
t.start()
print("主线程")
方法二:重写run方法
from threading import Thread
import time
class Sayhi(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
time.sleep(2)
print('%s say hello' % self.name)
if __name__ == '__main__':
t = Sayhi('nick')
t.start()
print('主线程')
6、GIL全局解释器锁
1. Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行
2. Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
为了实现上述,就有了GIL全局解释器锁
1、GIL锁它是在python解释器中的, 只在cpython中有, pypy解释器是没有GIL锁的,
2、起一个垃圾回收线程, 一个是正常执行的线程
3、设置了一把锁(GIL锁), 一个线程要想执行,必须拿到这把锁
4、同一时刻,开启一个进程,一个进程中可以有多个线程, 只能有一个线程在执行
5、如果是计算密集型:要开进程
6、如果是io密集型:要开线程
7、线程间数据是共享的
import os
from threading import Thread
def work():
global n
n=0
if __name__ =='__main__':
n = 1
t = Thread(target=work,)
t.start()
t.join()
print('主',n) # 查看结果为0,因为同一进程内的线程之间共享进程内的数据
8、守护进程
from threading import Thread
import time
def task():
time.sleep(1)
print("我是子线程")
if __name__ == '__main__':
t = Thread(target=task,)
t.setDaemon(True) # 开启守护线程, 主线程结束,子线程跟着结束
t.start()
print("主线程")
9、Thread类的方法
Thread实例对象的方法:
isAlive():返回线程是否活动的。
getName():返回线程名。
setName():设置线程名。
join():先运行完子线程,再运行做主线程
threading模块提供的一些方法:
threading.currentThread():返回当前的线程变量。
threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
10、多线程实现socket:
服务端:
import multiprocessing
import threading
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)
def action(conn):
while True:
data=conn.recv(1024)
print(data)
conn.send(data.upper())
if __name__ == '__main__':
while True:
conn,addr=s.accept()
p=threading.Thread(target=action,args=(conn,))
p.start()
p1=threading.Thread(target=action,args=(conn,))
p1.start()
客户端:
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',8080))
while True:
msg=input('>>: ').strip()
if not msg:continue
s.send(msg.encode('utf-8'))
data=s.recv(1024)
print(data)
11、信号量(Semaphore)
# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
from threading import Thread,Semaphore
import time
import random
sm=Semaphore(5)
def task(name):
sm.acquire()
print('%s正在蹲坑'%name)
time.sleep(random.randint(1,3))
sm.release()
if __name__ == '__main__':
for i in range(100):
t=Thread(target=task,args=(i,))
t.start()
12、Even事件
from threading import Thread, Event
import time
def girl(event):
print("都女士正在恋爱中...")
time.sleep(3)
print("都女生分手了")
# 发出分手的信号
event.set()
def boy(i, event):
print("渣男:%s正在等待都女生分手" % i)
# 卡住
event.wait()
print("渣男:%s开始追了" % i)
if __name__ == '__main__':
event = Event()
t = Thread(target=girl, args=(event,))
t.start()
for i in range(10):
b_t = Thread(target=boy, args=(i, event))
b_t.start()
13、同步锁(互斥锁)
from threading import Thread, Lock
import time
def task(lock):
# 上锁
lock.acquire()
global n
temp = n
# 并发安全
# 10个线程都卡在这了
time.sleep(1)
# n -= 1 # n=n-1
n = temp-1
# 释放锁
lock.release()
if __name__ == '__main__':
n = 10
ll = []
lock = Lock()
for i in range(10):
t = Thread(target=task, args=(lock, ))
t.start()
ll.append(t)
for j in ll:
j.join()
print("主线程:", n)
14、线程队列
queue队列:使用import queue,用法与进程Queue一样
同一个进程下多个线程数据是共享的
为什么同一个进程下还会去使用队列呢
因为队列是管道 + 锁
所以用队列还是为了保证数据的安全
1.先进先出
import queue
q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')
print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''
2.后进先出
import queue
q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')
print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''
3.优先级队列
import queue
q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))
print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''
4.Queue方法
Queue.qsize():返回队列中目前项目的正确数量
Queue.empty():如果队列是空的返回True
Queue.full():如果队列是满的返回True
Queue.put(item [, block [,timeout ] ] ) :将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,
默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。
timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
Queue.put_nowait(item):等价于put(item, False)
Queue.get( [ block [ ,timeout ] ] ):返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,
默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。
timeout是可选超时时间,用在阻塞模式中。
Queue.get_nowait():等价于get(False)
Queue.join(): block直到queue被消费完毕。
Queue.task_done():指示先前进入队列的任务已完成。 由队列消费者线程使用。 对于每个用于获取任务的get(),后续调用task_done()会告诉队列该任务的处理已经完成。
如果join()当前处于阻塞状态,那么当所有项都被处理完时,它将恢复正常(这意味着,对于每个已经被put()放入队列的项,都会收到一个task_done()调用)。
如果调用次数超过队列中放置的项的数量,则引发ValueError。

浙公网安备 33010602011771号