[Python-线程]

多任务实现方法:

  • 多进程
  • 多线程
  • 一个进程内创建多个线程

线程是操作系统直接支持的执行单元,因此,高级语言中大多内置了多线程的支持,Python的多线程是真正的Posix Thread,而不是模拟出来的多线程

Python多线程实现

Python提供两个高级库:_thread和thread,_thread是低级模块,thread是高级模块,对_thread进行了封装, 大多数情况下使用thread

两种方法使用线程:

  1. 传入一个函数
  2. 继承自threading.Thread的类

方法一: 启动一个线程的方法:把一个函数传入并创建Thread实例,然后调用start()函数开始执行:

import time, threading

# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

执行结果如下:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

方法二: 使用继承自threading.Thread的类

import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时,将一直阻塞知道获得锁定
        # 否则超时后将返回False
        threadLock.acquire()
        print_time(self.name, self.counter, 5)
        # 释放锁
        threadLock.release()
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有线程完成
for t in threads:
    t.join()
print "Exiting Main Thread"

执行结果如下:


[Python-线程]_第1张图片
Paste_Image.png

几点注意的地方:

  1. 上面的程序我们使用了Lock(线程锁),对资源进行独占,锁的使用方法如下:
    1. 创建一个锁实例: **threadLock = threading.Lock()
    2. 给需要独占的资源上锁:
    threadingLock.acquire() # 获得锁
    此处添加需要独占的资源
    threadingLock.release() # 资源用完后,需要释放锁
  2. 使用了join()函数,join()函数的作用总结如下:
    1. 阻塞主进程无法执行join以后的语句,专注执行多线程,必须等待多线程执行完毕之后才能执行主线程的语句。
    2. 多线程多join的情况下,依次执行各线程的join方法,前一个结束之后,才能执行后一个。(在上面的程序中,使用join()的这一条功能)
    3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。
    4. 设置参数后,则等待该线程N秒之后不管该线程是否结束,就开始执行后面的主进程。
  3. run()函数:在创建线程后会自动执行run()函数

Lock锁

多进程与多线程最大的区别在于:多进程中,同一个变量,各自有一份拷贝存在于进程中,互不影响(可以理解为:多进程中,每个进程都有一个堆栈空间存放各自的运行环境)而在多线程中,所有变量均有所有线程共享,因此,多线程最大的危险在于多个线程更改同一个变量,导致内容变乱

为了解决这个问题,python中有一个lock,可以让线程对某一个资源独享,通过

    lock = threading.Lock()实现
balance = 0
lock = threading.Lock()

def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

多个线程同时执行lock.acquire(),只有一个线程能够获得锁

获得锁的线程,用完了资源后,要及时释放锁,不然会导致其他线程一直被挂起,

死锁: 锁的机制可以确保资源的独占,但是也会使得某段代码只能以单线程的形式执行,不能并发,同时,不同线程可以有不同的锁,为了获得对方的锁,可能会导致死锁的情况,如:线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

三个示例:
1. 未使用锁的示例


# 未使用锁的情况
class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        print_time(self.name, self.counter, 5)
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()
print "Exiting Main Thread"

执行结果:

[Python-线程]_第2张图片
Paste_Image.png

几点注意:
在上面的程序中,我们未使用锁,也没有使用join()函数

  1. 从打印结果中可以看到,在最前面的三条打印中,显示的是:Starting Thread-1、Starting Thread-2和Exiting Main Thread。说明启动thread1和thread2之后,它们均进入到了sleep状态,这时继续往下执行了主线程,
    2.从打印中可以看到Thread-1和Thread-2是交替执行的,并不是执行完Thread-1的全部代码后再执行Thread-2

2. 使用锁,但不使用join()的示例

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时,将一直阻塞知道获得锁定
        # 否则超时后将返回False
        threadLock.acquire()
        print_time(self.name, self.counter, 5)
        # 释放锁
        threadLock.release()
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

print "Exiting Main Thread"

执行结果:

[Python-线程]_第3张图片
Paste_Image.png

几点注意

  1. 使用锁之后,只有等thread1执行完成并释放锁之后,才执行thread2的代码
  2. 主线程并没有等到thread1和thread2全部执行完成后才结束,而是在运行过程中逮到机会就直接运行

3. 第三个实例使用了锁和join()函数
该实例在文章上面的方法二: 使用继承自threading.Thread的类创建线程

小结

python解释器在执行代码时,有一个GIL锁:Global Interpreter Lock,任何python代码执行前必须获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,GIL锁,实际给所有线程的执行代码都上了锁,因此,多线程在python中只能交替执行

你可能感兴趣的:([Python-线程])