Python--多线程详解

1.1创建线程--_thread模块函数式线程

# -*- coding:utf-8 -*-
import _thread
import time
#_thread相对于threading功能较少,所以很少使用
#线程调用的方法
def print_time(threadName, delay):
    count = 0
    while count < 5:
        time.sleep(delay)
        count += 1
        print('%s : %s' % (threadName, time.ctime(time.time())))
def main():
    # try:
    _thread.start_new_thread(print_time, ("Thread-1", 0,))
        #创建线程并启动,_thread.start_new_thread(调用方法名,(threadName,delay))
    _thread.start_new_thread(print_time, ("Thread-2", 0,))
    # except:
    #     print("Error!Unable to start thread.")
if __name__ == '__main__':
    main() #跑main(),里面包含线程
    time.sleep(5)  #让主线程等待5s,不然子线程还在sleep,主线程已经跑完了,这也暴露了_thread模块没有阻塞方法的缺点
    print("1111")


#while1是一个循环,为了等待程序执行结束,不是一个好方法,占用CPU,已经被淘汰
# while 1:
#     # time.sleep(2)
#     # print("time1")
#     pass
import _thread
import time
from datetime import datetime

def Test(name):
    for i in range(3):
        print(name, datetime.now())
        time.sleep(1)
def main():
        _thread.start_new_thread(Test, ("one ",))
        _thread.start_new_thread(Test, ("two ",))

if __name__ == '__main__':
    start = time.time()
    main()
    end = time.time()
    time.sleep(5)
    print("运行程序花费了%s秒" % (end-start))

1.2创建线程--threading模块函数式线程

import threading
#threading模块的函数式线程
def test(name):
    count = 0
    while count < 3:
        print("name:%s " % count)
        count += 1
if __name__ == '__main__':
    print("11111")
    #第一个参数target是线程函数变量即函数名,第二个参数args是一个数组变量参数,
    #如果只传递一个值,就只需要t1, 
    # 如果需要传递多个参数,那么还可以继续传递下去其他的参数,其中的逗号不能少,
    # 少了就不是数组了,就会出错
    t1 = threading.Thread(target=test, args=('t1',))
    t2 = threading.Thread(target=test, args=('t2',))
    t1.start()//启动线程
    t2.start()

1.3创建线程--创建子类继承threading.thread

import threading
import time
class GetDetailHtml(threading.Thread):#创建一个子类继承threading.thread,自定义线程
    def __init__(self, name):
        super().__init__(name=name)#重构run函数必须写
        #下面两句和上面一句作用相同
        # threading.Thread.__init__(self)
        # self.name = name
    def run(self):  #重构run函数
        print("get detail html started")
        time.sleep(2)
        print("get detail html end")

class GetDetailUrl(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)
        #下面两句和上面一句作用相同
        # threading.Thread.__init__(self)
        # self.name = name

    def run(self):
        print("get details url started")
        time.sleep(2)
        print("get detail url end")
#
if __name__ == "__main__":
    thread1 = GetDetailHtml("get_detail_html")//创建线程1,括号内为类名!类名!
    thread2 = GetDetailUrl("get_detail_url")
    start_time = time.time()
    thread1.start()
    thread2.start()
    thread1.join()#阻塞,让主线程等待thread1运行完再继续运行
    thread2.join()
    print("last time :{}".format(time.time() - start_time))

1.4线程与锁与队列

import queue
import threading
import time
#threadID可以全部去掉,无意义
exitFlag = 0  #线程状态为运行

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print("开启线程:" + self.name)
        process_data(self.name, self.q)
        print("退出线程:" + self.name)

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()     #获取锁
        if not workQueue.empty():      #如果队列中存放的数据不为空
            data = q.get()             #将一个值从队列中取出
            #调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True.如果队列为空且block为True,get()就使调用线程
            # 暂停,直至有项目可用,如果队列为空且block为False,队列将引发Empty异常
            queueLock.release()  #释放锁
            print("%s processing %s" % (threadName, data))  #threadName为threaList内的元素,data为nameList内的元素
        else:
            queueLock.release() #释放锁
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five", "Six", "Six"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)  #队列中能存放的数据个数的上限
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1  #全部的ID都可以去掉没有意义

# 填充队列,遍历nameList里的数据,ll没有实质意义,只是为了去遍历nameList
queueLock.acquire()
for ll in nameList:
    workQueue.put(ll)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print("退出主线程")

2.1守护线程

setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此在主线程结束后,子线程也会结束。

import threading
import time

def run(n):
    print("task",n)
    time.sleep(1)#等待1s
    print("3")
    time.sleep(1)
    print("2")
    time.sleep(1)

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1", ))
    t.setDaemon(True)#把子线程设置为守护线程,必须在start()之前设置,设置后只要主线程结束子线程不管运行完没有都结束
    t.start()
    time.sleep(3)#让主线程等待3s,会看到子线程可以在主线程跑完之前跑完
    print("end")


在这里插入图片描述

2.2主线程等待子线程结束

import threading
import time

def run(n):
    print("task1", n)
    time.sleep(1)
    print("333333")
    time.sleep(1)
    print("222222")

if __name__ == '__main__':
    t1 = threading.Thread(target=run, args=("t1", ))
    t1.start()
    t1.join()#加一个阻塞,让主线程等子线程运行完,再结束
    print("end")

在这里插入图片描述

2.3多线程共享全局变量

import threading

g_num = 100
def work1():
    global g_num  #global是全局变量的标识符,global中出现的名字不能在global之前的代码中使用
    for i in range(3):
        g_num += 1
    print("in work1 g_num is : %d" % g_num)
def work2():
    # global g_num
    print("in work2 g_num is : %d" % g_num)
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t1.start()
    t2 = threading.Thread(target=work2)
    t2.start()

2.4互斥锁

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。

由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。

为了方式上面情况的发生,就出现了互斥锁(Lock)

from threading import Thread,Lock
import os, time
def work():
    global n #n为全局变量
    lock.acquire() #获取锁
    temp = n #temp取n的值,为100
    time.sleep(0.1)
    n = temp-1
    print("n = %d" % n)
    lock.release() #释放锁
if __name__ == '__main__':
    lock = Lock()#定义锁
    n = 100
    l = []
    for i in range(100):
        p = Thread(target=work)#创建线程p
        # append()用于在列表末尾添加新的对象,list.append(obj),list:列表对象,在这为l,
        #obj:添加到列表末尾的对象,在这为p;注意!!append()函数无返回值,但是会修改原本的列表
        l.append(p)
        print(l)
        p.start()
    for p in l:
        p.join()#让主线程等待所有子线程运行结束再结束

2.5递归锁


Rlock类和Lock类的用法一模一样,但他支持嵌套,在多个锁没有释放的时候会使用Rlock类

import threading
import time

def Func(lock):
    global gl_num #全局变量
    lock.acquire() #获得锁
    gl_num += 1 #叠加1
    time.sleep(1)
    print(gl_num)
    lock.release()#释放锁

if __name__ == '__main__':
    gl_num = 0
    lock = threading.RLock()#定义Rlock类
    for i in range(10):
        t = threading.Thread(target=Func, args=(lock,))#创建线程t
        t.start()

锁没释放的例子:

import threading
global x
x = 0
rlock = threading.RLock()
rlock.acquire()
x = x+1
#未释放锁
rlock.acquire()
x = x+2
rlock.release()
print("Displaying the final value of the shared resource x:", x)

在这里插入图片描述

3.1信号量(BoundedSemaphore类)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading
import time

def run(n, semaphore):
    semaphore.acquire()#加锁
    time.sleep(1)
    print("run the thread:%s\n" % n)
    semaphore.release()#放锁

if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(5)#最多允许5个线程同时运行
    for i in range(22):
        t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
        t.start()
    print(threading.active_count())#输出当前存活的线程数量,没有进入start()状态的不算
    while threading.active_count() != 1: #显示当前存活的线程数量,等于1时,表示当前只有一个主线程
        pass
    else:
        print('all threads done')

3.2事件(Event类)


python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:

clear 将flag设置为“False”
set 将flag设置为“True”
is_set 判断是否设置了flag
wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态
事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。

#用Event类模拟红绿灯
import threading
import time

event = threading.Event()#定义event类


def lighter():
    count = 0
    event.set()     #初始值为绿灯
    while True:
        if 5 < count <= 10:
            event.clear()  # 红灯,清除标志位,此时无标志位
            print("\33[41;1mred light is on...\033[0m")#输出红灯
        elif count > 10:
            event.set()  # 绿灯,设置标志位
            count = 0
        else:
            print("\33[42;1mgreen light is on...\033[0m")#前5s输出绿灯

        time.sleep(1)
        count += 1

def car(name):
    while True:
        if event.is_set():      #判断是否设置了标志位,如果有标志位,即绿灯,车可以走
            print("[%s] running..." % name)
            time.sleep(1)
        else:#无标志位,即红灯,车要停
            print("[%s] sees red light,waiting..." % name)#车看到红灯停止
            event.wait()#wait会一直监听,如果没有检测到flag就一直处于阻塞状态
            print("[%s] green light is on,start going..." % name)

light = threading.Thread(target=lighter,)#灯的线程
light.start()

car = threading.Thread(target=car, args=("MINI",))#车的线程
car.start()

3.3GIL Global Interpreter Lock全局解释器锁*

在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。

GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。

Python多线程的工作过程:
python在使用多线程的时候,调用的是c语言的原生线程。

拿到公共数据
申请gil
python解释器调用os原生线程
os操作cpu执行运算
当该线程执行时间到后,无论运算是否已经执行完,gil都被要求释放
进而由其他进程重复上面的过程
等其他进程执行完后,又会切换到之前的线程(从他记录的上下文继续执行),整个过程是每个线程执行自己的运算,当执行时间到就进行切换(context switch)。
python针对不同类型的代码执行效率也是不同的:

1、CPU密集型代码(各种循环处理、计算等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

使用建议?

python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。

GIL在python中的版本差异:

1、在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100时进行释放。(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
2、在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

本文参考https://www.cnblogs.com/luyuze95/p/11289143.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值