python——线程

Posted 小帅哥,快来玩呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python——线程相关的知识,希望对你有一定的参考价值。

线程

线程属于轻量级的进程

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

进程和线程的关系

 

 

  线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中,进程不是一个可执行的实体。

线程的特点

1)轻型实体

  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
  线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。

2)独立调度和分派的基本单位。

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

3)共享进程资源。

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

4)可并发执行。

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

 

全局解释器锁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模块

线程的创建Threading.Thread类

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print(\'%s say hello\' %name)

if __name__ == \'__main__\':
    t=Thread(target=sayhi,args=(\'egon\',))
    t.start()
    print(\'主线程\')
创建线程

 

守护线程

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

1.对主进程来说,运行完毕指的是主进程代码运行完毕

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

import time
from threading import Thread
def func():
    print(\'开始执行子线程\')
    time.sleep(3)
    print(\'子线程执行完毕\')

t = Thread(target=func)
t.setDaemon(True)      # 进程设置守护进程 是一个属性 daemon = True
t.start()
t2 = Thread(target=func)
t2.start()
t2.join()  # 等待t2结束


结果
\'\'\'
开始执行子线程
开始执行子线程
子线程执行完毕
子线程执行完毕

\'\'\'

GIL 不是锁数据 而是锁线程
在多线程中 特殊情况 仍然要加锁 对数据

import time
from threading import Thread
from threading import Lock
def func():
    global n
    time.sleep(2)
    lock.acquire()
    temp = n   # 从进程中获取n
    time.sleep(0.01)
    n = temp-1 # 得到结果,再存储回进程
    lock.release()

n = 100
lock = Lock()
t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)
[t.join() for t in t_lst]
print(n)
多线程占资源

死锁与递归锁

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

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

from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

 

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

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

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

信号量

同进程的一样

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

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

 

信号量 和 线程池 有什么区别?
相同点 在信号量acquire之后,和线程池一样 同时在执行的只能有n个
不同点
开的线程数不一样 线程池来说 一共就只开5个线程 信号量有几个任务就开几个线程
对有信号量限制的程序来说 可以同时执行很多线程么?
实际上 信号量并不影响线程或者进程的并发,只是在加锁的阶段进行流量限制

事件

同进程的一样

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

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

条件

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

import threading
def run(n):
    con.acquire()
    con.wait()  # 等着
    print("run the thread: %s" % n)
    con.release()

if __name__ == \'__main__\':
    con = threading.Condition()   # 条件  = 锁 + wait的功能
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()

    while True:
        inp = input(\'>>>\')
        if inp == \'q\':
            break
        con.acquire()        # condition中的锁 是 递归锁
        if inp == \'all\':
            con.notify_all()
        else:
            con.notify(int(inp))   # 传递信号 notify(1) --> 可以放行一个线程
        con.release()
案例

定时器

from threading import Timer

def hello():
        print("hello, world")
while True:    # 每隔一段时间要开启一个线程
    t = Timer(10, hello)   # 定时开启一个线程,执行一个任务
                      # 定时 : 多久之后 单位是s
                      # 要执行的任务 :函数名

线程队列

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

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

class queue.Queue(maxsize=0) #先进先出
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
\'\'\'
先进先出

class queue.LifoQueue(maxsize=0) #last in fisrt out

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
\'\'\'
先进先出

class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

值越小越优先,值相同就asc码小的先出

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\')
\'\'\'
优先级队列

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)
回调函数

 

以上是关于python——线程的主要内容,如果未能解决你的问题,请参考以下文章

python threading超线程使用简单范例的代码

[Python3] 043 多线程 简介

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

python中的多线程和多进程编程

常用python日期日志获取内容循环的代码片段

多线程 Thread 线程同步 synchronized