python线程

主要内容:

1 线程的Thread模块
2 同步控制 : 锁 事件 信号量 条件 定时器

 

线程:

进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。
 

线程的特点:

在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。

线程具有以下属性。

一.轻型实体

线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。

线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。

TCB包括以下信息:
(1)线程状态。
(2)当线程不运行时,被保存的现场资源。
(3)一组执行堆栈。
(4)存放每个线程的局部变量主存区。
(5)访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

二.独立调度和分派的基本单位。

在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。

三.共享进程资源。

线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

四.可并发执行。

在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

 

 

线程进程之间的对比

 

线程与进程的区别可以归纳为以下4点:

 

  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

 

  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

 

  3)调度和切换:线程上下文切换比进程上下文切换要快得多。

 

  4)在多线程操作系统中,进程不是一个可执行的实体。
 
经典问题:
进程好 还是 线程好?或者什么时候用多进程 什么时候用多线程?
完整回答:
"""
当程序处于并发时会考虑是多线程好,还是多进程好.
这个要分程序的情况:
如果是多cpu计算时,采用多进程比较好.
如果有io(也就是涉及到网络,文件和数据库时)用多线程
如果涉及到大的并发,比如2000时,同时有高cpu计算以及io时,这时用多进程+多线程.
"""

 

 

 

全局解释器锁GIL

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

 

 在多线程环境中,Python 虚拟机按以下方式执行:

 

  a、设置 GIL;

 

  b、切换到一个线程去运行;

 

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

 

  d、把线程设置为睡眠状态;

 

  e、解锁 GIL;

 

  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

 

 

线程的创建

用threading包来创建.

示例代码如下:

import time 
from threading import Thread

def fun(num):
    num -= 1
    print(num)

for i in range(20):
    t = Thread(target= fun,args=(i,))
    t.start()

 

 也可以用类来创建,如下:

import time,os,random
from threading import Thread

def fun(num):
    num = num -1
    print(num)

class Mythread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self):
        fun(int(self.name))
for i in range(20):
    t = Mythread(i)
    t.start()

 

 

多线程与多进程的比较

pid的比较

import os, time, random
from multiprocessing import Process
from threading import Thread


def process_fun(num, flag):
    print("%s,%s 的id是%s" % (flag, num, os.getpid()))


if __name__ == "__main__":
    p = Process()
    for i in range(5):
        p = Process(target=process_fun, args=(i, "进程"))
        p.start()
    for i in range(5):
        t = Thread(target=process_fun, args=(i, "线程"))
        t.start()

开启效率的较量

import os,time,random
from multiprocessing import Process
from threading import Thread

def fun(i):
    print(i)

if __name__ == "__main__":
    t1 = time.time()
    p_lst = []
    for i in range(50):
        p = Process(target = fun,args= (i,))
        p.start()
        p_lst.append(p)
    for i in p_lst:i.join()
    t2 = time.time()
    print("线程",t2-t1)
    t3 =time.time()
    for i in range(50):
        t = Thread(target= fun,args = (i,))
        t.start()
    t4 = time.time()
    print("线程",t4-t3)

内存数据的共享问题

示例:

import time,os,random
from threading import Thread

n = 100
def fun():
    global n
    n = 0

t = Thread(target = fun)
t.start()
print(n)

练习 :多线程实现socket

socket端代码:

import socket
import time, os, random
from threading import Thread

sk = socket.socket()
ip_addr = ("127.0.0.1", 9000)
sk.bind(ip_addr)
sk.listen()



def fun(conn):
    try:
        while 1:
            ret = conn.recv(1024)
            print(ret)
            conn.send(b"hi")
    except ConnectionAbortedError:
        pass
    finally:
        conn.close()


while True:
    conn, addr = sk.accept()
    t = Thread(target=fun, args=(conn, ))
    t.start()

sk.close()

客户端代码:

import socket
sk  = socket.socket()
ip_addr = ("127.0.0.1",9000)
sk.connect(ip_addr)
sk.send(b"hello")
ret = sk.recv(1024)
print(ret)
sk.close()

 

 

Thread类的其他方法

获取线程的id   get_ident()

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。


threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。  这个可以通过from threading import
currentThread
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 示例方法:

from threading import Thread,get_ident
import threading
import os,time

def fun(i):
    print("第%s个线程,id是%s"%(i,get_ident()))
    time.sleep(0.1)



for i in range(10):
    t = Thread(target = fun,args = (i,))
    t.start()
    print(t.is_alive())
    print("***",t)
    print("---",threading.currentThread())
    print(t.getName())
print(threading.currentThread().getName())   #主线程
print(threading.enumerate())
print(threading.activeCount())

线程的join()方法

import os
from threading import Thread
def fun(i):
    print("第%s个线程"%i)
t_lst = []
for i in range(20):
    t = Thread(target=fun,args=(i,))
    t.start()
    t_lst.append(t)
for i in t_lst:i.join()
print("主线程")

守护线程

无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行

#1.对主进程来说,运行完毕指的是主进程代码运行完毕
#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread
import time

def fun(i):
    time.sleep(2)
    print("进程是",i)

for i in range(10):
    t = Thread(target=fun,args=(i,))
    t.setDaemon(True)
    t.start()

print("主进程")  
"""
注意这里主进程是不会等守护进程的
"""

示例2:

from threading import Thread
import time

def fun(i):
    while 1:
        time.sleep(0.5)
        print("daemon进程",i)

def fun2(i):
    time.sleep(6)
    print("进程是",i)

t = Thread(target=fun,args=("daemon",))
t.setDaemon(True)
t.start()


t = Thread(target=fun2,args=("daemon",))
t.start()

print("主进程")

#注意主进程会等其他非守护进程

 

 


线程中复现锁的代码 :

from threading import Thread
import time

def fun():
    global n

    tmp = n
    time.sleep(0.1)
    n = tmp -1


t_lst = []
n = 100
for i in range(10):
    t = Thread(target=fun)
    t.start()
    t_lst.append(t)
for i in t_lst:i.join()
print(n)

 

用同步锁来解决上面的问题,如下代码,这样会变成串行的.

from threading import Thread,Lock
import time

def fun():
    global n
    lock.acquire()
    tmp = n
    time.sleep(0.1)
    n = tmp -1
    lock.release()


t_lst = []
n = 100
lock = Lock()
for i in range(10):
    t = Thread(target=fun)
    t.start()
    t_lst.append(t)
for i in t_lst:i.join()
print(n)

互斥锁和join的区别:

#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    global n
    print('%s is running' %current_thread().getName())
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''


#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    #未加锁的代码并发运行
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    #加锁的代码串行运行
    lock.acquire()
    temp=n
    time.sleep(0.5)
    n=temp-1
    lock.release()

if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

#有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
#没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
#start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
#单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''

 

 

死锁与递归锁

 

 

# 死锁
# 多把锁同时应用在多个线程中
# 互斥锁和递归锁哪个好
# 递归锁 快速恢复服务
# 死锁问题的出现 是程序的设计或者逻辑的问题
# 还应该进一步的排除和重构逻辑来保证使用互斥锁也不会发生死锁
# 互斥锁和递归锁的区别
# 互斥锁 就是在一个线程中不能连续多次ACQUIRE
# 递归锁 可以在同一个线程中acquire任意次,注意acquire多少次就需要release多少次

 

进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,
如下就是死锁.

 单线程的话,可能因为程序原因产生死锁,如下:

from threading import Thread,Lock
lock = Lock()
lock.acquire()
print(123)
lock.acquire()
print(456)
lock.release()
lock.release()

这个解决办法就是修改代码逻辑,或者用递归锁来解决,如下:

from threading import Thread,RLock
r = RLock()
r.acquire()
r.acquire()
print(123)
r.release()
r.release()

递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

 

 

典型问题:科学家吃面

复现死锁问题:

#科学家吃面问题

from threading import Thread,Lock
import time
noodle_lock = Lock()
fork_lock = Lock()

def fun(name):
    fork_lock.acquire()
    print("%s拿到了叉子"%name)
    noodle_lock.acquire()
    print("%s拿到了面"%name)
    fork_lock.release()
    noodle_lock.release()
    print("%s吃面成功"%name)

def fun1(name):
    noodle_lock.acquire()
    print("%s拿到了面"%name)
    time.sleep(1)
    fork_lock.acquire()
    noodle_lock.release()
    print("%s拿到了叉子"%name)
    fork_lock.release()

for i in range(10):
    t = Thread(target=fun,args=(i,))
    t.start()
    t = Thread(target=fun1,args=(i,))
    t.start()

 

from threading import Thread,Lock,RLock
import time
noodle_lock = fork_lock = RLock()
# fork_lock = RLock()
"""
注意:
这里这样不行,
noodle_lock =  RLock()
fork_lock = RLock()
必须完全相等
"""

def fun(name):
    fork_lock.acquire()
    print("%s拿到了叉子"%name)
    noodle_lock.acquire()
    print("%s拿到了面"%name)
    fork_lock.release()
    noodle_lock.release()
    print("%s吃面成功"%name)

def fun1(name):
    noodle_lock.acquire()
    print("%s拿到了面"%name)
    time.sleep(1)
    fork_lock.acquire()
    noodle_lock.release()
    print("%s拿到了叉子"%name)
    fork_lock.release()

for i in range(10):
    t = Thread(target=fun,args=(i,))
    t.start()
    t = Thread(target=fun1,args=(i,))
    t.start()

信号量

同进程的一样

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

import os
from threading import Semaphore,Thread
import threading
import time
sem = Semaphore(5)

def fun(sem,i):
    sem.acquire()
    time.sleep(1)
    print("第%s个线程正在执行!"%i)
    sem.release()

for i in range(20):
    t = Thread(target=fun,args=(sem,i))
    t.start()
print("主线程")

 

 

池与信号量的区别:

与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

 

# 池 和 信号量
# 池 效率高
# 池子里有几个一共就起几个
# 不管多少任务 池子的个数是固定的
# 开启进程和关闭进程这些事都是需要固定的开销
# 就不产生额外的时间开销
# 且进程程池中的进程数控制的好,那么操作系统的压力也小
# 信号量
# 有多少个任务就起多少进程/线程
# 可以帮助你减少操作系统切换的负担
# 但是并不能帮助你减少进/线程开启和关闭的时间

 

事件:

同进程的一样

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行.

event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。

例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

使用事件来控制线程

import os,time,random
from threading import Event,Thread
e = Event()

def conn_sql(n):
    count = 1
    while not e.is_set():
        if count < 4 :
            print("%s正在链接数据库......"%n)
            e.wait(0.5)
            count += 1
        else:
            print("链接超时")
            return
    print("数据库连接成功!")
def check_sql(n):
    print("正在检查数据库....")
    time.sleep(random.random())
    # time.sleep(0.6)
    e.set()
for i in range(5):
    t = Thread(target=conn_sql,args=(i,))
    t.start()
t = Thread(target=check_sql,args=("1"))
t.start()

 

 

 

条件

使得线程等待,只有满足某条件时,才释放n个线程

详细说明:

Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

参考示例如下:

from threading import Thread,Condition
con = Condition()
def fun(n):
    con.acquire()
    con.wait()
    print("第%s个线程正在执行"%n)
    con.release()

for i in range(50):
    Thread(target=fun,args=(i,)).start()
while 1:
    try:
        num = input(">>>>")
        con.acquire()
        con.notify(int(num))
        con.release()
    except ValueError:
        print("输入错误,请重新输入!")

定时器

定时器,指定n秒后执行某个操作(这里要注意是线程已经开启了,线程里面的内空会在指定时间后执行!)

from threading import Timer
def hello():
    print("hello,world!")
t = Timer(2,hello)
t.start()
print(t.is_alive())

 

线程队列

queue队列 :使用import queue,用法与进程Queue一样

class queue.Queue(maxsize=0) #先进先出

示例:

from queue import Queue
q = Queue(3)
q.put(123)
q.put(456)
q.put(789)
#q.put(000)
print(q.get())
print()

后进先出

示例:

from queue import LifoQueue
q = LifoQueue(3)
q.put(1111)
q.put(2222)
q.put(3333)
# q.put(4444)
print(q.get())

优先级队列:

取值是按一开始传值的优先级来传值.(如果优先级一致,那么就按字符编码表来看)

示例:

from queue import PriorityQueue
q = PriorityQueue()
q.put((10,"a"))
q.put((20,"b"))
q.put((5,"c"))
print(q.get())

 

 

 
# 线程池

 

Python标准模块--concurrent.futures

 

 

#1 介绍
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.

#2 基本方法
#submit(fn, *args, **kwargs)
异步提交任务

#map(func, *iterables, timeout=None, chunksize=1) 
取代for循环submit的操作

#shutdown(wait=True) 
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

#result(timeout=None)
取得结果

#add_done_callback(fn)
回调函数
ProcessPoolExecutor的用法:
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import  ThreadPoolExecutor
import time
def fun(n):
    print("%s正在执行!"%n)
    time.sleep(0.1)
    return n**2
def back(i):
    print("*****",i.result())
if __name__ == '__main__':

    t = ProcessPoolExecutor(3)
    for i in range(20):
        ret = t.submit(fun,i)
        print(ret.result())

    t.shutdown()
    print("主进程")

以及示例:

#用法
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ProcessPoolExecutor(max_workers=3)

    futures=[]
    for i in range(11):
        future=executor.submit(task,i)
        futures.append(future)
    executor.shutdown(True)
    print('+++>')
    for future in futures:
        print(future.result())

map的使用方法:

from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import  ThreadPoolExecutor
import time
def fun(n):
    print("%s正在执行!"%n)
    time.sleep(0.1)
    return n**2
def back(i):
    print("*****",i.result())
if __name__ == '__main__':
    t_lst = []
    t = ProcessPoolExecutor(3)
    # for i in range(20):
    #     ret = t.submit(fun,i)
    #     t_lst.append(ret)
        # ret

    t.map(fun,range(20))
    t.shutdown()

    print("主进程")

回调函数

参考如下:

from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import  ThreadPoolExecutor
import time
def fun(n):
    print("%s正在执行!"%n)
    time.sleep(0.1)
    return n**2
def back(i):
    print("*****",i.result())
# if __name__ == '__main__':
#     t_lst = []
#     t = ProcessPoolExecutor(3)
#     # for i in range(20):
#     #     ret = t.submit(fun,i)
#     #     t_lst.append(ret)
#         # ret
#
#     t.map(fun,range(20))
#     t.shutdown()
#
#     print("主进程")
if __name__ == '__main__':
    t = ThreadPoolExecutor(3)
    for i in range(30):
        t.submit(fun,i).add_done_callback(back)
    t.shutdown()
    print("主线程 ")

 

 


posted @ 2018-07-30 15:56  auxiaoliu  阅读(154)  评论(0编辑  收藏  举报