Python-多线程

梦想的初衷 提交于 2020-03-08 13:01:47

多线程

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

  thread在python3中被废弃了
  python3中threading代替thread模块
  为了兼容性 python3将thread改名为 “_thread”
  python的标准库提供了两个模块 ,_thread(低级模块) 和 threading(高级模块)

1.threading

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

import threading #导入线程模块
import time
 
#线程函数
def foo(num):
    print('我的进程名字为:'+threading.current_thread().name)
    time.sleep(2)
    print(num)
 
#创建进程  #callback:回调函数 一般用来接收子进程运行函数的返回值
t1 = threading.Thread(target=foo,args=(1,),callback=func)
t2 = threading.Thread(target=foo,args=(2,))
 
#启动线程
t1.start()
t2.start()
 
#获取线程运行状态 True为运行 False为未运行
print(t1.is_alive())
print(t2.is_alive())
 
#等待所有线程运行完毕在继续往下执行
t1.join()
t2.join()

结果为:

我的进程名字为:Thread-1
我的进程名字为:Thread-2
True   #运行状态
True   #运行状态
1      #第一个线程打印
2      #第二个线程打印

参数说明:

threading.current_thread( ) 它返回当前线程的实例
join( )在子线程执行完成之前,这个子线程的父线程将一直被阻塞。就是说,当调用join()的子进程没有结束之前,主进程不会往下执行。对其它子进程没有影响。
is_alive() 获取线程的运行状态

2.线程类:

* 参数可以是一个变量,也可以是个容器

```python
import threading

class MyThread(threading.Thread):
    def __init__(self,fileName):
        threading.Thread.__init__(self)
        self.fileName = fileName
    # 当实例对象调用start()方法时,就会触发run()方法
    def run(self):
        print('开启线程,下载%s文件'%self.fileName)


if __name__ == "__main__":
    t = MyThread('test.jpg')
    t.start()

3.守护进程setDaemon(True)

将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就兵分两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法。

例:

import threading
from time import ctime,sleep
import time
 
def music(func):
    for i in range(2):
        print ("Begin listening to %s. %s" %(func,ctime()))
        sleep(4)
        print("end listening %s"%ctime())
 
def move(func):
    for i in range(2):
        print ("Begin watching at the %s! %s" %(func,ctime()))
        sleep(5)
        print('end watching %s'%ctime())
 
threads = []
t1 = threading.Thread(target=music,args=('七里香',))
threads.append(t1)
t2 = threading.Thread(target=move,args=('阿甘正传',))
threads.append(t2)
 
if __name__ == '__main__':
 
    for t in threads:
        t.setDaemon(True)
        t.start()
 
 
    print ("all over %s" %ctime())

结果为:

Begin listening to 七里香. Thu Sep 29 15:45:32 2016    #t1和t2启动,分别打印一次后sleep,主进程继续
Begin watching at the 阿甘正传! Thu Sep 29 15:45:32 2016
all over Thu Sep 29 15:45:32 2016              #主进程结束,程序结束

4.Lock互斥锁

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

import threading
 
num = 0
def add():
    global  num
    for  i in range(1000000):
            num += 1
 
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
 
t1.start()
t2.start()
 
t1.join()
t2.join()
 
print(num)

结果为:

1183554

为什么会出现这样的问题呢 上面已经说过了,线程间任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。这种情况要使用lock (互斥锁)

  互斥锁:当多个线程同时修改一个共享数据的时候,需要进行同步控制
  线程同步能够保证多个线程安全访问'竞争资源',最简单的同步机制就是引用互斥锁
锁的过程:
  互斥锁为资源引入一个状态,锁定/非锁定状态
  某个线程要更改共享资源时,现将其锁定,此时资源的状态为'锁定',其他线程不能更改
  知道当前线程释放资源,将资源变为'非锁定'装填,其他的线程才能再次锁定该资源
  互斥锁保证了每次只有一个线程进行'写操作',从而保证多个线程数据正确性
import threading
 
num = 0
def add():
    global  num
    for  i in range(1000000):
        lock.acquire()#上锁
        num += 1
        lock.release()#解锁
        #也可以使用with方法效果相同
        # with lock:
        #     num += 1
 
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
#创建互斥锁 只能有一个进程同时访问
lock = threading.Lock()
t1.start()
t2.start()
 
t1.join()
t2.join()
 
print(num)

这时的结果为:

2000000

 参数:lock.acquire(True/False)

  True:如果所要获取的资源已经'锁定',表示当前线程处于等待状态(阻塞).知道获取到这个锁为止 --默认值
  Flase : 不阻塞,即不管本次调用能都成功上锁,都不会卡在这里,而是继续执行后面的代码
 互斥锁的好处和坏处:
  锁的好处
    确定某段代码只能由一个线程从头到尾完整的执行
    全局变量的安全问题
  坏处
    阻止了多线程的并发执行,包含锁的某段代码实际上只能以单线程模块执行,效率大大降低
    由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁时,可能会造成'死锁'

       死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源时,就会造成死锁
      尽管死锁很少发生,但一旦发生就会造成程序停止响应

    避免死锁
      程序设计师要尽量避免死锁(银行家算法)
      添加超时时间
    银行家算法

5.信号量

信号量是允许同一时间同时几个线程访问共享变量

import threading,time
 
def foo(i):
    # 上锁
    sem.acquire()
    time.sleep(1)
    print(i)
    # 解锁
    sem.release()
 
# 锁 --- 信号量
sem = threading.BoundedSemaphore(3)
 
for i in range(10):
    t = threading.Thread(target=foo,args=(i,))
    t.start()

自己体验执行结果会更容易理解信号量

6.Event(事件)

小伙伴a,b,c围着吃火锅,当菜上齐了,请客的主人说:开吃!,于是小伙伴一起动筷子,这种场景如何实现?

Event(事件):事件处理的机制:全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True,那么event.wait 方法时便不再阻塞。Event没有锁,无法使线程进入同步阻塞状态。
set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。
clear(): 将标志设为False。
wait(timeout): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。
isSet(): 获取内置标志状态,返回True或False。
import threading
import time
 
event = threading.Event()
 
def chihuoguo(name):
    # 等待事件,进入等待阻塞状态
    print '%s 已经启动' % threading.currentThread().getName()
    print '小伙伴 %s 已经进入就餐状态!'%name
    time.sleep(1)
    event.wait()
    # 收到事件后进入运行状态
    print '%s 收到通知了.' % threading.currentThread().getName()
    print '小伙伴 %s 开始吃咯!'%name
 
# 设置线程组
threads = []
 
# 创建新线程
thread1 = threading.Thread(target=chihuoguo, args=("a", ))
thread2 = threading.Thread(target=chihuoguo, args=("b", ))
 
# 添加到线程组
threads.append(thread1)
threads.append(thread2)
 
# 开启线程
for thread in threads:
    thread.start()
 
time.sleep(0.1)
# 发送事件通知
print '主线程通知小伙伴开吃咯!'
event.set()

结果为:

Thread-1 已经启动
小伙伴 a 已经进入就餐状态!
Thread-2 已经启动
小伙伴 b 已经进入就餐状态!
主线程通知小伙伴开吃咯!
Thread-1 收到通知了.
小伙伴 a 开始吃咯!
Thread-2 收到通知了.
小伙伴 b 开始吃咯!

7.线程池

使用queue和threading创建一个线程池由于threading没有自带线程池 所以我们需要手动写

from queue import Queue
import threading
from multiprocessing import Pool
import time
 
class ThreadPool(object):
    def __init__(self,max_num):
        # 设置线程队列
        self.thread_q = Queue(max_num)
 
        # 往队列里添加线程类对象
        for i in range(max_num):
            self.thread_q.put(threading.Thread)
 
    # 获取一个线程
    def get_thread(self):
        return self.thread_q.get()
 
    # 添加一个线程
    def add_thread(self):
        self.thread_q.put(threading.Thread)
 
# 使用线程池
def foo(i,pool):
    print(i)
    time.sleep(1)
    pool.add_thread()
 
if __name__ == '__main__':
    # 创建线程池
    pool = ThreadPool(2)
 
    # 循环创建任务
    for i in range(1,11):
        # 从线程池拿取一个线程
        Thread = pool.get_thread()
        # 创建线程任务
        t = Thread(target=foo,args=(i,pool))
        # 启动任务
        t.start()

8.Threading.Local

threadLocal解决了参数在一个线程中各个函数之间相互传递的问题

一个threadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本 互不干扰

import threading
#创建全局ThreadindLocal对象
local = threading.local()
class Stu():
    def __inif__(self,name):
        self.name = name
def process_stu(name):
    std  = (name)
    #向对象中添加属性 这个属性值谁调用就是谁的
    local.stu = std
    do_task_1()
    do_task_2()
def do_task_1():
    std = local.stu
    print('do_task_1',std)
 
def do_task_2():
    std = local.stu
    print('do_task_2',std)
 
if __name__ == "__main__":
    t1 = threading.Thread(target=process_stu,args=('小明',))
    t2 = threading.Thread(target=process_stu,args=('小亮',))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

  全局变量local,每个线程对它都可以读写`stu`属性 而且互不影响
  可以把local看成全局变量,但每个属性如local.stu都是线程的局部变量
  可以任意读写互不干扰,也不用管理锁的问题(threading内部处理)
  可以理解为全局变量local是一个字典 不但可以用local.stu 还可以绑定其他变量
  threadLocal最常用的地方就是为每一个线程绑定一个数据库线程,Http请求,用户身份信息等
  这样一个线程的所有调用到的处理函数都可以非常方便的访问这些资源 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!