第 11 课 Python 多线程

1. 进程与线程

        进程就是程序执行的载体,我们打开的每个软件、游戏,执行的每一个Python脚本都是启动一个进程。
        线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位。例如车间的生产是一个进程,那每个流水线就是它的一个线程。
        线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
        进程提供线程执行程序的前置要求,线程在重组的资源配备下,去执行程序。Python3线程中常用的两个模块是“_thread”和“threading”(推荐使用)。
        thread模块已被废弃。用户可以使用threading模块代替。所以,在Python3中不能再使用“thread”模块。为了兼容性,Python3将thread重命名为“_thread”。

2. 线程对象Thread

class threading.Thread(group=None,target=None,name=None,args=(),kwargs={})
  1. group: 应该为None,为了日后扩展ThreadGroup类实现而保留
  2. target: 是用于run()方法调用的课调用对象,默认是None,表示不需要调用任何方法
  3. name:线程名称,默认情况下,由"Thread-N"格式构成一个唯一的名称,其中N是小的十进制数。
  4. args:用于调用目标函数的参数元组,默认是()
  5. kwargs:用于调用目标函数的关键字参数字典,默认是{}

3. Thread类的方法

join

       阻塞直到线程执行结束。这会阻塞调用这个方法的线程,直到被调用join的线程终结-不管是正常终结还是抛出未处理异常-或者直到发生超时,超时选项是可选的。

       一定要在join()后调用is_alive()才能判断是否发生超时-如果线程仍然存活,则join()超时。一个线程可以被join()很多次

join(timeout=none)
getName 获取线程的名字 getName()
setName 设置线程的名字 setName(name)
is_alive 判断线程是否存活 is_alive()

 

setDaemon

守护进程 setDaemon(True)

4. 创建Thread对象

4.1 直接创建Thread

        将一个函数对象从类的构造器传递进去,这个对象就是回调函数,用来处理任务

        

from threading import Thread
def task():
    print( "I am Sub_Thread")
if __name__== "__main__":
    t = Thread(target=task)
    t.start()
    print("I am Main_Thread")
结果打印如下:
I am Sub_Thread
I am Main_Thread

4.2 继承Thread类

        编写一个自定义类继承Thread,然后重写run()方法,在run()方法中编写任务处理代码,然后创建这个Thread的子类。

from threading import Thread
class MyThread(Thread):
    def run(self):
        print("I am Sub_Thread")
if __name__== "__main__":
    t = MyThread()
    t.start()
    print("I am Main_Thread")
结果打印如下:
I am Sub_Thread
I am Main_Thread

5. 多线程

        多线程类似于车间生产时,有多条流水线在运作,可以提高生产的效率。创建Thread对象,然后让它们运行,每个Thread对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
        这里创建了7个线程执行任务,同时调用join方法等待线程执行结束,比单一主线程执行更加快速

import threading
def func(n):
    while n>0:
        print( "CurrentThread Num:{0}: CurrentThread Name:{1} ".format(threading.activeCount(),threading.currentThread().name))
        n-=1

threads = []
for x in range(5):
    t = threading.Thread(target=func ,args=(2,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print("MainThread:",threading.currentThread().name)
结果打印如下:
CurrentThread Num:2: CurrentThread Name:Thread-2 
CurrentThread Num:3: CurrentThread Name:Thread-2 
CurrentThread Num:2: CurrentThread Name:Thread-3 
CurrentThread Num:2: CurrentThread Name:Thread-3 
CurrentThread Num:2: CurrentThread Name:Thread-4 
CurrentThread Num:3: CurrentThread Name:Thread-4 
CurrentThread Num:2: CurrentThread Name:Thread-5 
CurrentThread Num:3: CurrentThread Name:Thread-6 
CurrentThread Num:3: CurrentThread Name:Thread-6 
CurrentThread Num:2: CurrentThread Name:Thread-5 
MainThread: MainThread

6. 线程同步

        如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。使用Thread对象的Lock和Rlock可以实现简单的线程同步。

 6.1 LOCK锁

  1. acquire(blocking=True, timeout=-1):使线程进入同步阻塞状态,尝试获得锁定。当调用时参数blocking设置为True,阻塞直到锁被释放,然后将锁锁定并返回True。在参数blocking被设置为False,将不会发生阻塞。
  2.  release():释放锁。使用前线程必须已获得锁定,否则将抛出异常。release()只在锁定状态下调用,它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发RuntimeError异常。对于Lock对象而言,如果一个线程连续两次release,会使得线程死锁。所以Lock不常用,一般采用Rlock进行线程锁的设定。
def job1():
    global A , lock
    lock.acquire()
    for i in range(10):
        A+=1
        print("Job1",A)
    lock.release()

def job2():
    global A ,lock
    lock.acquire()
    for i in range(10):
        A+=10
        print("Job2",A)
    lock.release()

if __name__ == "__main__":
    lock= threading.Lock()
    A=0
    t1 = threading.Thread(target=job1)
    t2 = threading.Thread(target=job2)

    t1.start()
    t2.start()
    t1.join()
    t2.join()
结果打印如下:
Job1 1
Job1 2
Job1 3
Job1 4
Job1 5
Job1 6
Job1 7
Job1 8
Job1 9
Job1 10
Job2 20
Job2 30
Job2 40
Job2 50
Job2 60
Job2 70
Job2 80
Job2 90
Job2 100
Job2 110

        执行后,t1先获得锁,执行完成后再释放锁,t2再获得锁,继续执行后再释放锁,这样避免同时处理,导致数据出错。

 6.2 RLock锁

  1.  acquire(blocking=True, timeout=-1):可以阻塞或非阻塞地获得锁,当无参数调用时,如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。
            一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值。
  2. release():释放锁,自减递归等级。如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则仅允许其中一个线程继续。如果自减后,递归等级仍然不是零,则锁保持锁定,仍由调用线程拥有。
           RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。
           RLock 相当于包含一个锁定池和一个初始值为0 的计数器, 每次成功调用acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。
lock = threading.RLock()
ret = lock.acquire()
print(ret)
ret = lock.acquire(timeout=3)
print(ret)
ret = lock.acquire(True)
print(ret)
ret = lock.acquire(False)
print(ret)
结果打印如下:
True
True
True
True

 

 

你可能感兴趣的:(Python入门,开发语言,python)