Python——线程


线程

线程:

  1. 是操作系统能够进行运算调度的最小单位
  2. 他包含在进程中
  3. 是进程中的实际运作单位
  4. 进程是线程的容器
线程:实现多任务的另一种方式
  1. 一个进程中,也经常需要同时做多件事,就需要同时运行多个‘子任务’,这些子任务,就是线程
  2. 线程又被称为轻量级进程(lightweight process),是更小的执行单元
  3. 一个进程可拥有多个并行的(concurrent)线程,当中每一个线程,共享当前进程的资源
  4. 一个进程中的线程共享相同的内存单元/内存地址空间可以访问相同的变量和对象,而且它们从同一堆中分配对象通信、数据交换、同步操作
  5. 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快

总结:

线程,进程

  1. 线程存在于进程当中,是操作系统能够调用的最小单元,它包含在进程中.
  2. 一个程序至少有一个进程,一个进程至少有一个线程
  3. 一个进程可以有多个线程,

2.进程:程序运行的过程。

进程——线程的区别:

  • 进程用来分配和管理资源:线程用来执行操作
  • 进程开销比较大,线程的开销比较小
  • 线程执行开销小, 但不利于资源的管理和保护;而进程正相反
  • 一个进程可拥有多个并行的线程,当中每一个线程,共享当前进程的资源
    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

4.进程用来分配空间(从来分配资源),线程用来执行操作
5.在一个进程内的所有线程共享全局变量, 多线程之间的数据共享(这点要比多进程要好)
缺点就是, 可能造成多个线程同时修改一个变量(即线程非安全),可能造成混乱

线程的创建:

  • python的thread模块是比较底层的模块,在各个操作系统中表现形式不同(低级模块)
  • python的threading模块是对thread做了⼀些包装的, 可以更加⽅便的被使⽤(高级模块)
  • thread 有一些缺点,在threading 得到了弥补,所以我们直接学习threading
import threading
if __name__ == "__main__":
    #任何进程默认会启动一个线程,这个线程称为主线程,主线程可以启动新的子线程
    #current_thread():返回当前线程的实例
    #.name :当前线程的名称
    print('主线程%s启动' %(threading.current_thread().name))
import threading,time
def saySorry():
    print("子线程%s启动" %(threading.current_thread().name))
    time.sleep(1)
    print("亲爱的,我错了,我能吃饭了吗?")

if __name__ == "__main__":
    print('主线程%s启动' %(threading.current_thread().name))
    for i in range(5):
        t = threading.Thread(target=saySorry))#Thread():指定线程要执行的代码
        t.start()
查看当前线程数量:
import threading
import time

def sing():
    for i in range(3):
        print("正在唱歌...%d" %i)
        time.sleep(1)
def dance():
    for i in range(2):
        print("正在跳舞...%d" %i)
        time.sleep(1)
if __name__ == "__main__":
    print("开始:%s" %time.time())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        #threading.enumerate():返回当前运行中的Thread对象列表
        print("当前线程数为:%d" %length)
        if length<=1:
            break
        time.sleep(1)

创建线程的两种方式:

第一:通过 threading.Thread 直接在线程中运行函数;
第二:通过继承 threading.Thread 类来创建线程
这种方法只需要重载 threading.Thread 类的 run 方法,然后调用 start()开启线程就可以了

import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print(i)
if __name__ == "__main__":
    t1 = MyThread()
    t2 = MyThread()
    t1.start()
    t2.start()
import threading
import time
class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msq = "I`m" + self.name + "@" + str(i)
            #name属性中保存了当前线程的名字
            print(msq)
if __name__ == "__main__":
    t = MyThread()
    t.start()
线程的五种状态:

1.新状态:线程对象已经创建,还没有在其上调用start()方法。

  • 可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
  • 运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
  • 等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的(可运行的),但是当前没有条件运行。但是如果某件事件出现,他可能返回到可运行状态。
  • 死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
线程共享全局变量:
  • 在一个进程内的所有线程共享全局变量, 多线程之间的数据共享(这点要比多进程要好)
  • 缺点就是, 可能造成多个线程同时修改一个变量(即线程非安全),可能造成混乱
import threading
import time

num = 100
def work1():
    global num
    for i in range(3):
        num += 1
    print("---in work1,num is %d" %num)
def work2():
    global num
    print("---in work2,num is %d" %num)
print("---线程创建之前 num is %d" %num)
t1 = threading.Thread(target=work1)
t1.start()
time.sleep(1)
#延时一会保证线程1中的任务做完
t2 = threading.Thread(target=work2)
t2.start()
import threading,time
def work1(nums):
    nums.append(44)
    print('-----in work1-----' ,nums)
def work2(nums):
    time.sleep(1)
    #延时一会保证另一线程执行
    print('-----in work2-----', nums)

g_nums = [11,22,33]
t1 = threading.Thread(target=work1,args=(g_nums,))
t1.start()
t2 = threading.Thread(target=work2,args=(g_nums,))
t2.start()
执行1000000次的bug:
import threading
num = 0
def test1():
    global num
    for i in range(100):#一百万错误
	    num += 1
def test2():
    global num
    for i in range(100):#一百万错误
	    num += 1
p1 = threading.Thread(target=test1)
p1.start()
p2 = threading.Thread(target=test2)
p2.start()
print("---num = %d---" %num)

线程同步:

  • 当多个线程几乎同时修改某一个共享数据的时候, 需要进行同步控制
  • 线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引入互斥锁
    互斥锁保证了每次只有一个线程进行写入操作,从⽽保证了多线程情况下数据的正确性(原子性)
  • 互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
  • threading模块中定义了Lock类, 可以方便的处理锁定
创建锁mutex = threading.Lock()
锁定mutex.acquire()
释放mutex.release()  #解锁
import threading
num = 0
def test1():
    global num
    if mutex.acquire():
        for i in range(1000):
            num += 1
    mutex.release()
def test2():
    global num
    if mutex.acquire():
        for i in range(1000):
            num += 1
    mutex.release()
mutex = threading.Lock()
p1 = threading.Thread(target=test1)
p1.start()
p2 = threading.Thread(target=test2)
p2.start()
print(num)
  • 当一个线程调用Lock对象的acquire()方法获得锁时,这把锁就进入“locked”状态。
    因为每次只有一个线程可以获得锁,所以如果此时另一个线程2试图获得这个锁,该线程2就会变为同步阻塞状态
  • 直到拥有锁的线程1调用锁的release()方法释放锁之后,该锁进入“unlocked”状态。
    线程调度程序继续从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态,一个线程有锁时,别的线程只能在外面等着

线程同步(死锁):

死锁(错误情况,理解即可) 在线程间共享多个资源的时候, 如果两个线程分别占有一部分资源并且同时等待对方的资源, 就会造成死锁
在这里插入图片描述

import threading
import time
class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name + '---do1---up---')
            time.sleep(1)
            if mutexB.acquire():
                print(self.name + '---do1---down---')
                mutexB.release()
        mutexA.release()
class MyThread2(threading.Thread):
    def run(self):
	time.sleep(1) 
	if mutexB.acquire():
            		print(self.name + '---do2---up---')
            		if mutexA.acquire():
			print(self.name + '---do2---down---')
                		mutexA.release()
            	mutexB.release()

if __name__ == '__main__':
    mutexA = threading.Lock()
    mutexB = threading.Lock()
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
  • 在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的。如一个线程获取了第一个锁,然后在获取第二个锁的时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死(两个人每人一根筷子)

信号量:

  • 信号量semaphore :用于控制一个时间点内线程进入数量的锁,信号量是用来控制线程并发数的
  • 使用场景举例:在读写文件的时候,一般只有一个线程在写,而读可以有多个线程同时进行,如果需要限制同时读文件的线程个数,这时候就可以用到信号量了 (如果用互斥锁,就是限制同一时刻只能有一个线程读取文件)
import time
import threading

def foo():
    time.sleep(2)
    print("ok",time.ctime())

for i in range(100):
    t1=threading.Thread(target=foo) 
    t1.start()  #此时无法控制同时进入的线程数
import time
import threading
s1=threading.Semaphore(5)      (赛么佛)
def foo():
    s1.acquire()    
    time.sleep(2)   
    print("ok")
    s1.release()    
for i in range(20):
    t1=threading.Thread(target=foo,args=()) 
    t1.start()    #此时可以控制同时进入的线程数

全局解释器锁(GIL):

解释器分为,Cpython,Jpython,Pypy

  • 全局解释器锁只有Cpython有,其它解释器没有,什么是全局解释器锁,在解释器级别上,上了一把锁,假设,我有三个CPU,有一个进程,三个线程,在正常情况下,每个线程占用一个CPU这样效率高,现在有了全局解释器锁,他要求你,在多线程的时候多个线程用一个CPU,所以不能发挥多核CPU的优势,Python中的多线程是伪的多线程
  • 优势是:数据跟安全,从解释器界别上保证了数据的安全性,
  • 还有一个问题,为什么还需要一个互斥锁哪,因为:在加互斥锁时,不是解释器级别的数据,自己写的数据,即全局解释锁保护的数据更加底层的
  • 缺点:CPU的作用是做运算,不管开多少条线程,如果计算,因为多个线程用一个CPU所以效率斌没有提高,现在假设需要做运算,有三个线程,三个CPU,有全局解释器锁,只能用一个CPU所以效率没有,提高而且降低了,因为来回的切换也占用时间,如果是计算不用多线程,如果I/O操作,用多线程。
  • 为什么,说I/O操作用多线程,因为I/O操作不予CPU打交道,
解析:全局解释器锁
  • 操作系统将.py文件,从硬盘加载到内存,运行python文件,因为程序运行一定有一个进程,在这个进程空间中将python解释器以及.py文件都的加载到里面,python解释器逐行解释.py文件,实现了程序的运行,Python解释器分为两部分,首先把你的代码通过编译器,编译成C的字节码,因为CPython底层就是那C语言写的,写的python代码实际上计算机根本不认识,即把python代码转成C语言的代码,然后用C语言的编译器成字节码的文件,再交给计算机的CPU去运行,在.py文件中有一个主线程,以上都是主线程做的,这也是为什么Python运行起来比较慢。
  • 最理想的情况:假设有三个线程,三个CPU同时去执行,这样就有效的利用的CPU的资源,但是Cpython有一个全局解释器锁这个锁还是互斥锁,导致同一时间只有一个线程在执行,发挥不了,多核优势,

为什么这么干,因为当时写python的时候,只有一个CPU,没有多核CPU
为什么不取消:

  • 因为Cpython都是针对单线程写的。
  • Cpython:运行速度慢,还有一个就是GIL
    Python属于解释型语言。每次运行都要解释一遍。执行速度慢,开发效率快。
    C语言编译型语言。

总结全局解释器锁:

  • 操作系统将.py文件,从磁盘加载到内存,运行python文件,python解释器逐行解释.py文件,首先把python代码装换成C语言的代码,然后用C语言的解释器编译成字节码文件,交给CPU去运行。最理想的情况:假设有三个线程,三个CPU同时去执行,这样就有效的利用的CPU的资源,但是Cpython有 一个全局解释器锁这个锁还是互斥锁,导致同一时间只有一个线程在执行,发挥不了,多核优势。

同步调用:确定调用的顺序
提交一个任务,自任务开始运行直到此任务结束,我再提交下一个任务
按顺序购买四大名著
异步调用:不确定顺序
一次提交多个任务,然后我就直接执行下一行代码
你喊你朋友吃饭 ,你朋友说知道了 , 待会忙完去找你 ,你就去做别的了

给三个老师发布任务:
同步: 先告知第一个老师完成写书的任务,我原地等待,等他两个月之后完成了,我再发布下一个任务......

异步:直接将三个任务告知三个老师,我就忙我的我,直到三个老师完成之后,告知我

同步意味着顺序、统一的时间轴

  • 场景1:是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,协同步调,按预定的先后次序进行运行
  • 场景2:一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
    线程同步-多个线程有序执行:
import threading,time
class Task1(threading.Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print('-----Task1-----')
                time.sleep(1)
                lock2.release()
class Task2(threading.Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print('-----Task2-----')
                time.sleep(1)
                lock3.release()
class Task3(threading.Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print('-----Task3-----')
                time.sleep(1)
                lock1.release()
lock1 = threading.Lock()
#创建另外一把锁,并且锁上
lock2 = threading.Lock()
lock2.acquire()
#再创建一把锁并锁上
lock3 = threading.Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()     t2.start()       t3.start()

Python的Queue模块:实现了3种类型的队列来实现线程同步,包括:
FIFO(先进先出) 队列 Queue,
LIFO(后进先出) 栈 LifoQueue,
优先级队列 PriorityQueue
区别在于队列中条目检索的顺序不同
在FIFO队列中,按照先进先出的顺序检索条目
在LIFO队列中,最后添加的条目最先检索到(操作类似一个栈)
在优先级队列中,条目被保存为有序的(使用heapq模块)并且最小值的条目被最先检索
这些队列都实现了锁原语(可以理解为原⼦操作, 即要么不做, 要么就做完) , 能够在多线程中直接使用
现阶段只要求掌握其中一种,FIFO队列

生产者消费者模式:

  • 在线程世界中, 生产者就是生产数据的线程, 消费者就是消费数据的线程(做包子,吃包子)
  • 经常会出现生产数据的速度大于消费数据的速度,或者生产速度跟不上消费速度
  • 生产者消费者模式是通过一个容器(缓冲区)来解决生产者和消费者的强耦合问题 例如两个线程共同操作一个列表,一个放数据,一个取数据
  • 生产者和消费者彼此之间不直接通讯, 而通过阻塞队列来进行通讯

线程同步--消息队列:

class queue.Queue(maxsize=0)
  • FIFO队列的构造器。maxsize为一个整数,表示队列的最大条目数,可用来限制内存的使用
  • 一旦队列满,插入将被阻塞直到队列中存在空闲空间。如果maxsize小于等于0,队列大小为无限。maxsize默认为0
import threading
import time
from queue import Queue
class Pro(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            if queue.qsize()<1000:
                for i in range(100):
                    count = count + 1
                    msg = '生成产品' + str(count)
                    queue.put(msg)#队列中添加新产品
                    print(msg)
            time.sleep(1)
class Con(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name + '消费了' + queue.get()
                    print(msg)
            time.sleep(1)
if __name__ == "__main__":
    queue = Queue()
    #创建一个队列。线程中能用,进程中不能使用
    for i in range(500):#创建500个产品放到队列里
        queue.put(‘初始产品’ + str(i))#字符串放进队列
    for i in range(2):#创建了两个线程
        p = Pro()
        p.start()
    for i in range(5):#5个线程
        c = Con()
        c.start()
异步则意味着乱序、效率优先的时间轴
  • 处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、回调来通知调用者处理结果
  • 对于I/O相关的程序来说,异步编程可以大幅度的提高系统的吞吐量,因为在某个I/O操作的读写过程中,系统可以先去处理其它的操作(通常是其它的I/O操作)
  • 不确定执行顺序
异步1,无需等待线程执行:
import threading, time
def thead(num):
    print("线程%s开始执行" % num)
    time.sleep(3)
    print("线程%s执行完毕" % num)


print("主方法开始执行")
poll = []
for i in range(1, 3):
    thead_one = threading.Thread(target=thead, args=(i,))
    poll.append(thead_one)
for n in poll:
    n.start()  # 准备就绪,等待cpu执行

print("主方法执行完毕")
异步2,通过循环控制
import threading,time
num = 0
def test1():
    global num
    while True:
        if mutex.acquire(False):
            for i in range(1000000):
                num += 1
            print("1", num)
            mutex.release()
            break
        else:
            print("该干嘛干嘛")
def test2():
    global num
    while True:
        if mutex.acquire(False):
            for i in range(1000000):
                num += 1
            print("2", num)
            mutex.release()
            break
        else:
            print("该干嘛干嘛")
mutex = threading.Lock()
p1 = threading.Thread(target=test1)
p2 = threading.Thread(target=test2)
p1.start()
p2.start()
异步3,通过回调机制:
from multiprocessing import Pool
import random
import time
def download(f):
    for i in range(1,4):
        print(f"{f}下载文件{i}")
        time.sleep(random.randint(1,3))
    return "下载完成"

def alterUser(msg):
    print(msg)
if __name__ == "__main__":
    p = Pool(3)
    # 当func执行完毕后,return的东西会给到回调函数callback
    p.apply_async(func= download,args=("线程1",),callback = alterUser)
    p.apply_async(func=download, args=("线程2",),callback =alterUser)
    p.apply_async(func=download, args=("线程3",),callback =alterUser)
    p.close()
    p.join()
posted @ 2020-08-30 13:19  知秋一叶9527  阅读(176)  评论(0编辑  收藏  举报