python 线程理解

和 java 一样,线程的理解相对于进程而言,区别就是线程是共享 状态,资源和内存,属于轻量级的,方便,但是也带来了死锁,争用条件和高复杂性在内的各种问题。


python线程的创建:import threading t = threading.Thread(target='方法名',args=('元祖参数信息',))

有关 Thread()参数为:
def__init__(self, group=None, target=None, name=None, args=(), kwargs={})

  参数group是预留的,用于将来扩展;
  参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行;
  参数name是线程的名字。默认值为“Thread-N“,N是一个数字。
  参数args和kwargs分别表示调用target时的参数列表和关键字参数。

t.start() 开始运行

t.setDaemon() setDaemon(True)将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。

创建 Thread 继承类:

class MyThread(threading.Thread()): 注意里边必须有run() 方法 这里才是执行的核心,同时需要在外部调用该对象的 start 方法来让线程得到执行。


Thread方法说明

t.start() : 激活线程,

t.getName() : 获取线程的名称

t.setName() : 设置线程的名称

t.name : 获取或设置线程的名称

t.is_alive() : 判断线程是否为激活状态

t.isAlive() :判断线程是否为激活状态

t.setDaemon() 设置为后台线程或前台线程(默认:False);通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

t.isDaemon() : 判断是否为守护线程

t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。

t.join() :逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义

t.run() :线程被cpu调度后自动执行线程对象的run方法

import datetime,threading
from time import sleep
class MyThread(threading.Thread):
def run(self):
print('%s says at time = %s' %(self.getName(),datetime.datetime.now()))
sleep(3)

for t in range(2):
t = MyThread()
t.start()
t.join() #这里的join是让子线程得到完全的执行后才执行父线程 不然父执行完,子没得执行了
print('end time = %s' %datetime.datetime.now())


Thread类还定义了以下常用方法与属性:

Thread.getName()
Thread.setName()
Thread.name

用于获取和设置线程的名称。


Thread.ident
获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。

Thread.is_alive()
Thread.isAlive()
判断线程是否是激活的(alive)。从调用start()方法启动线程,到run()方法执行完毕或遇到未处理异常而中断 这段时间内,线程是激活的。

Thread.join([timeout])
调用Thread.join将会使主调线程堵塞,直到被调用线程运行结束或超时。参数timeout是一个数值类型,表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束。

join()
保持阻塞状态,直到处理了队列中的所有项目为止。在将一个项目添加到该队列时,未完成的任务的总数就会增加。当使用者线程调用 task_done() 以表示检索了该项目、并完成了所有的工作时,那么未完成的任务的总数就会减少。当未完成的任务的总数减少到零时,join() 就会结束阻塞状态。


下面是一个封装线程的例子:

from time import ctime,sleep
import threading

class MyThread(threading.Thread):
def __init__(self,func,args={}):
print('开始初始化 线程对象,使用的方法名为: %s 参数为: %s' %(func,args))
threading.Thread.__init__(self)
self.func = func
self.args = args

def run(self):
threads = []
for m,k in self.args.items():
t = threading.Thread(target=self.func,args=(m,k))
threads.append(t)
print('一共需要的线程分发数量: %s 开始执行线程.....' %(len(threads)))
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join() #这里保证子进程的完全执行


list_data = {'热血高校.mp4':2,'舞动青春.mp3':5}
def testThread(name,time):
for m in range(2): ##每个线程都自动分发两条
print('start play is name = %s sleep = %s nowtime = %s' %(name,time,ctime()))
sleep(time)

m = MyThread(testThread,list_data)
m.start() # 调用start 开始执行我的线程

print('all is over %s ' %ctime())


下面不得不谈一谈有关锁的问题,由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。为了保证数据的准确性,引入了锁的概念。所以,可能出现如下问题:

例:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1,那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。


例如未加锁前:

import threading
import time

globals_num = 0 # 公共资源

def Func():
global globals_num
globals_num += 1
time.sleep(1) # 因为这里每次都睡1秒,实际执行的时候根本等不到这里执行完就开始下一条线程
print(globals_num) # 最后的结果就是这里始终打印 10 因为第10此线程结束后才开始子线程的sleep

for i in range(10):
t = threading.Thread(target=Func)
t.start() #这样会导致每次新的线程持有资源时的资源信息都不一样 即每次都+1

结果 10 10 10 10 10 10 10 10 10 10


加锁后:

import threading
import time

globals_num = 0
lock = threading.RLock()

def Func():
lock.acquire() #获得锁
global globals_num
globals_num += 1
time.sleep(1)
print(globals_num)
lock.release() #释放锁

threads = []
for i in range(10):
t = threading.Thread(target=Func)
threads.append(t)
t.start() ## 题外话 本来这里加上 t.join() 也能得到效果,但是意义不一样,因为那样的话就实际上属于单线程了,注意 join 不能与 start 在循环里连用。

结果 1 2 3 4 5 6 7 8 9 10

顺便谈谈join用法:
错误例子:

threads = [Thread() for i in range(5)]
for thread in threads:
     thread.start()
     thread.join()

执行过程:
1. 第一次循环中,主线程通过start函数激活线程1,线程1进行计算.
2. 由于start函数不阻塞主线程,在线程1进行运算的同时,主线程向下执行join函数.
3. 执行join之后,主线程被线程1阻塞,在线程1返回结果之前,主线程无法执行下一轮循环.
4. 线程1计算完成之后,解除对主线程的阻塞.
5. 主线程进入下一轮循环,激活线程2并被其阻塞…

如此往复,可以看出,本来应该并发的五个线程,在这里变成了顺序队列,效率和单线程无异.


正确用法:

使用两个循环分别处理start和join函数.即可实现并发.但是注意资源的锁

threads = [Thread() for i in range(5)]
for thread in threads:
     thread.start()
for thread in threads:
     thread.join()

顺便说一句 千万注意锁的全局性,不然就无法生效。即

lock = threading.Lock()

必须全局使用

"""
 threading  Condition 例子
"""

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

在典型的设计风格里,利用condition变量用锁去通许访问一些共享状态,线程在获取到它想得到的状态前,会反复调用wait()。修改状态的线程在他们状态改变时调用 notify() or notify_all(),用这种方式,线程会尽可能的获取到想要的一个等待者状态。 例子: 生产者-消费者模型,

Lock是threading模块提供的锁对象,Lock默认创建的是一个锁对象,当我们需要对全局对象进行操作的时候,可以通过Lock创建对象来锁定对象,Lock对象就好比java中的synchronize(aObject)代码中的aObject对象。

而condition除了具有Lock对象的acquire方法和release方法外,还有wait、notify、notifyAll方法等用于条件处理。Condition对象可以在某些事件触发或者达到特定条件后才处理数据。很像java中锁一个对象后,对象调用notify或者notifyAll方法去触发操作。Condition还支持从外界引用一个Lock对象。


import threading
import time
"""
生产者 消费者模式
"""
def consumer(cond,name):
    with cond:
        print("consumer before wait name = %s and time =  %s" %(name,time.ctime()))
        cond.wait()
        print("consumer after wait name = %s and time =  %s" %(name,time.ctime()))

def producer(cond):
    with cond:
        print("producer before notifyAll %s " %time.ctime())
        cond.notifyAll()  #这里唤醒 子线程的阻塞
        print("producer after notifyAll %s " %time.ctime())

condition = threading.Condition()
c1 = threading.Thread(name="c1", target=consumer, args=(condition,'thread-1')) #创建消费者线程1  condition 传参
c2 = threading.Thread(name="c2", target=consumer, args=(condition,'thread-2'))  # 创建消费者线程2 condition 传参
p = threading.Thread(name="p", target=producer, args=(condition,)) #创建生产者线程  codition 传参

c1.start()
time.sleep(2)
c2.start()
time.sleep(2)
p.start()



再谈谈 threading.RLock和threading.Lock 的区别

RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。 如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

有关 threading.Event

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

clear:将“Flag”设置为False
set:将“Flag”设置为True
Event.isSet() :判断标识位是否为Ture。

import threading

"""
线程 Event 例子
"""

def do(event,name):
    print('start')
    event.wait()   ##这里开始阻塞
    print('execute %s' %name)


event_obj = threading.Event()  # 这里创建 Event 对象
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,'任意参'))  # 这里将Event 对象作为参数传入线程列表
    t.start()  #这里的start 只会执行到 方法内 event.wait() 之前,之后的需要再次调用event.set 方法才能执行下去

event_obj.clear()
inp = input('input:')
if inp == 'true':
    event_obj.set()

"""
线程 定时器的例子
"""
from threading import Timer

def fun():
    print "hello, world"

if __name__=='__main__':
    t = Timer(5.0, fun)
    t.start() # 开始执行线程,但是不会打印"hello, world"
    t.cancel() # 因为cancel取消了线程的执行,所以fun()函数不会被执行

有关队列的用法:

import queue

q = queue.Queue(maxsize=0)  # 构造一个先进显出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。

q.join()    # 等到队列为kong的时候,在执行别的操作
q.qsize()   # 返回队列的大小 (不可靠)
q.empty()   # 当队列为空的时候,返回True 否则返回False (不可靠)
q.full()    # 当队列满的时候,返回True,否则返回False (不可靠)
q.put(item, block=True, timeout=None) #  将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置,
                         为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后,
                          如果队列无法给出放入item的位置,则引发 queue.Full 异常
q.get(block=True, timeout=None) #   移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,
                      若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。
q.put_nowait(item) #   等效于 put(item,block=False)
q.get_nowait() #    等效于 get(item,block=False)


我的线程池创建,不知道对不对,暂时贴上吧


import threading,queue
from time import ctime,sleep

"""
线程池   首先要有线程池线程内最高个数
其次  要有队列来存放线程
3 要有对于线程池的个数大小的判断 如果低于一定数量就创建
4 队列本身属于安全 不需要加锁
5 要有一个get  一个set 方法
"""
class ThreadPool():
    def __init__(self,max_num=10,min_num=3):
        self.que = queue.Queue(max_num)
        self.min_num = min_num
        #开始创建固定线程数
        for m in range(max_num):
            self.que.put(threading.Thread)
        print('创建出线程队列 个数 %s '%self.que.qsize())

    def currentT(self):
        return self.que.qsize()

    def getThread(self):
        current_t = self.currentT()
        if current_t <self.min_num:
            print(' 当前线程数: %s 小于规定数:%s 开始增加.....' %(current_t,self.min_num))
            self.addThread(3)
        return self.que.get()


    def addThread(self):
        self.que.put(threading.Thread)

    def addThread(self,num):
        for m in range(num):
            self.que.put(threading.Thread)
        print('新增线程数: %s ' %3)

def func(t_name,args):
    print('%s  你来了,开始玩耍吧 你带来了 %s '%(t_name,args))

if __name__=="__main__":
    t = ThreadPool()
    for m in range(20):
        thread = t.getThread()
        print('当前线程数量:%s' %t.currentT())
        tt = thread(target=func,args=('线程'+str(m),'随便参数'))
        tt.start()



借用网上一个例子:

#coding:utf-8
#---- Condition
#---- 捉迷藏的游戏
import threading, time

#找
class Hider(threading.Thread):
    def __init__(self, cond, name):
        super(Hider, self).__init__()
        print('准备去找人....')
        self.cond = cond
        self.name = name
    def run(self):
        time.sleep(1) #确保先运行Seeker中的方法
        print('hider 开始 获取锁...')
        self.cond.acquire() #b
        print(self.name + ': 我已经把眼睛蒙上了')
        print('hander 开始唤醒...')
        self.cond.notify()
        print('hander开始等待....')
        self.cond.wait() #c
        print(self.name + ': 我找到你了 ~_~')
        self.cond.notify()
        self.cond.release()
                            #g
        print(self.name + ': 我赢了')   #h

#藏
class Seeker(threading.Thread):
    def __init__(self, cond, name):
        super(Seeker, self).__init__()
        print('准备藏起来.....')
        self.cond = cond
        self.name = name
    def run(self):
        print('seeker 开始 获取锁...')
        self.cond.acquire()  #获取锁对象
        print('seeker 开始等待...')
        self.cond.wait()    #a    #释放对琐的占用,同时线程挂起在这里,直到被notify并重新占有琐。
        print(self.name + ': 我已经藏好了,你快来找我吧')
        print('sekker 开始唤醒')
        self.cond.notify()
        print('seeker 开始等待')
        self.cond.wait()    #e

        self.cond.release()
        print(self.name + ': 被你找到了,哎~~~')
cond = threading.Condition()  #创建线程控制器
seeker = Seeker(cond, 'seeker')
hider = Hider(cond, 'hider')
seeker.start()
hider.start()


猜你喜欢

转载自blog.csdn.net/guoxinjie17/article/details/78105949
今日推荐