当前位置: 移动技术网 > IT编程>脚本编程>Python > Python之多线程设计实例讲解

Python之多线程设计实例讲解

2018年03月05日  | 移动技术网IT编程  | 我要评论

yanqingwuyuetian,中巴友谊公路,观赏石

◆ 进程:

An executing instance of a program is called a process.

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

多道程序设计:

在计算机内存中同时存放几道相互独立的程序,使它们在管理程序控制之下,相互穿插的运行。 两个或两个以上程序在计算机系统中同处于开始到结束之间的状态。这就称为多道程序设计。多道程序技术运行的特征:多道、宏观上并行、微观上串行。

★ 进程的缺陷:

进程有很多缺陷,主要体现在两点上:

 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?

这就需要线程来解决。

◆ 线程:

操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

原理:CPU给你一种幻觉,它在同时做多个计算操作(执行多个线程)。实际上,CPU记录了每个线程的执行上下文,然后在多个线程中进行快速切换。

A thread is an execution context, which is all the information a CPU needs to execute a stream of instructions., while a process is a bunch of resources associated with a computation.

A process can have one or many threads.

◆ 进程与线程的区别:

1、同一进程中的不同线程,可以共享进程的地址空间;不同的进程间独占自己的地址空间
2、同一进程中的不同线程,可以直接获得进程的数据;进程只能从父进程中复制一份数据给自己使用。
3、同一进程中的不同线程,可以直接和其他线程交互;进程只能通过进程内通讯和其他进程交互。
4、新的线程很容易被创建;新进程的创建需要复制父进程。
5、同一进程中的不同线程,可以实现相互控制;进程只能控制其子进程。
6、主线程的变化可以改变其他线程的运行;父进程的改变不会影响子进程。

Python GIL(Global Interpreter Lock):

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。

这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:https://www.dabeaz.com/python/UnderstandingGIL.pdf

 

◆ 线程调用的两种方式:

★ 直接调用,将要执行的方法作为参数传给Thread的构造方法

def action(arg):
    time.sleep(1)
    print 'the arg is:%s\r' %arg

for i in xrange(4):
    t =threading.Thread(target=action,args=(i,))
    t.start()

print 'main thread end!'

★ 继承式调用,从Thread继承,并重写run()

# coding:utf-8
import threading
import time

class MyThread(threading.Thread):
    def __init__(self,arg):
        super(MyThread, self).__init__()#注意:一定要显式的调用父类的初始化函数。
        self.arg=arg
    def run(self):#定义每个线程要运行的函数
        time.sleep(1)
        print 'the arg is:%s\r' % self.arg

for i in xrange(4):
    t =MyThread(i)
    t.start()

print 'main thread end!'

◆ 构造方法:

Thread(group=None, target=None, name=None, args=(), kwargs={}) 
  group: 线程组,目前还没有实现,库引用中提示必须是None; 
  target: 要执行的方法; 
  name: 线程名; 
  args/kwargs: 要传入方法的参数。

◆ 实例方法:
  

★ isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。 
★ get/setName(name): 获取/设置线程名。 
★ is/setDaemon(bool): 获取/设置是后台线程(默认前台线程(False))。(在start之前设置)
  如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,主线程和后台线程均停止
   如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
★ start(): 启动线程。 
★ join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。

◆ is/setDaemon(bool):

serDeamon(True)后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。

示例:

# coding:utf-8
import threading
import time

def action(arg):
    time.sleep(1)
    print  'sub thread start!the thread name is:%s\r' % threading.currentThread().getName()
    print 'the arg is:%s\r' %arg

for i in xrange(4):
    t =threading.Thread(target=action,args=(i,))
    t.setDaemon(True)#设置线程为后台线程
    t.start()

print 'main_thread end!'

执行结果:

main_thread end!

◆ join()

阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout,即使设置了setDeamon(True)主线程依然要等待子线程结束。

#coding:utf-8
import threading
import time

def action(arg):
    time.sleep(arg)
    print  'the sub thread name is:%s    ' % threading.currentThread().getName()
    print 'the sleep time is:%ss   ' %arg

thread_list = []    #线程存放列表
for i in xrange(2):
    t =threading.Thread(target=action,args=(i,))
    t.setDaemon(True)
    thread_list.append(t)

for t in thread_list:
    t.start()

for t in thread_list:
    t.join()

print 'main_thread end!'

执行结果:

the sub thread name is:Thread-1    
the sleep time is:0s   
the sub thread name is:Thread-2    
the sleep time is:1s   
main_thread end!

join不妥当的用法,使多线程编程顺序执行:

#coding:utf-8
import threading
import time

def action(arg):
    time.sleep(1)
    print  'sub thread start!the thread name is:%s    ' % threading.currentThread().getName()
    print 'the arg is:%s   ' %arg

for i in xrange(2):
    t =threading.Thread(target=action,args=(i,))
    t.setDaemon(True)
    t.start()
    t.join()

print 'main_thread end!'

运行结果:

sub thread start!the thread name is:Thread-1    
the arg is:0   
sub thread start!the thread name is:Thread-2    
the arg is:1 

注:共运行了2秒才结束!!每个线程都被上一个线程的join阻塞,使得“多线程”失去了多线程意义。

◆ 线程的锁

由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。

Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。

可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。

RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。

可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。

简言之:Lock属于全局,Rlock属于线程。

构造方法:

Lock()
Rlock()

注:推荐使用Rlock()

实例方法:

★ acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。 
★ release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

★ 使用示例:

# coding:utf-8

import threading
import time

gl_num = 0
lock = threading.RLock()


# 调用acquire([timeout])时,线程将一直阻塞,
# 直到获得锁定或者直到timeout秒后(timeout参数可选)。
# 返回是否获得锁。
def Func():
    lock.acquire()
    global gl_num
    gl_num += 1
    time.sleep(1)
    print gl_num
    lock.release()


for i in range(4):
    t = threading.Thread(target=Func)
    t.start()

运行结果:

1
2
3
4

全局变量在在每次被调用时都要获得锁,才能操作,因此保证了共享数据的安全性

◆ Lock对比Rlock

★ Lock

#coding:utf-8
import threading

lock = threading.Lock() #Lock对象

lock.acquire()
lock.acquire()  #产生了死锁。
lock.release()
lock.release()

print lock.acquire()

★ Rlock(递归锁)

# coding:utf-8
import threading

rLock = threading.RLock()  #RLock对象
rLock.acquire()
rLock.acquire() #在同一线程内,程序不会堵塞。
rLock.release()
rLock.release()

◆Semaphore(信号量)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程进行操作。

示例:

# coding:utf-8
import threading, time

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


if __name__ == '__main__':
    semaphore = threading.BoundedSemaphore(2)  # 最多允许2个线程同时运行

    for i in range(6):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass  # print threading.active_count()
else:
    print('----all threads done---')

执行结果:

run the thread: 0
run the thread: 1


run the thread: 3
run the thread: 2


run the thread: 5
run the thread: 4


----all threads done---

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网