并发编程 之 线程 之 Thread 模块(数据共享, 守护线程), 锁Lock(递归锁), 信号量, 事件, 条件, 定时器 (二)

Thread 模块 from threading import Thread:

与进程的效率差别:

import time
from threading import Thread
from multiprocessing import Process
# 效率差别
def func(a):
    a = a + 1

if __name__ == '__main__':
    start = time.time()
    t_l = []
    for i in range(50):
        t = Thread(target=func,args=(i,))
        t.start()
        t_l.append(t)
    # t_l 50个线程对象
    for t in t_l : t.join()
    print('主线程')
    print(time.time() - start)

    start = time.time()
    t_l = []
    for i in range(50):
        t = Process(target=func, args=(i,))
        t.start()
        t_l.append(t)
    # t_l 50个线程对象
    for t in t_l: t.join()
    print('主进程')
    print(time.time() - start)



'''
跑一下后, 所显示的 时间差, 会非常明显的显出来

之所以会这样, 是因为 
      对于多进程来说:
            需要打开多个进程, 每个进程和进程之间都是独立的空间互不干扰, 所以在打开和关闭的时候 会消耗大量的时间, 
      对于 多线程来说:
            相当于只打开了一个进程, 在一个进程中实现了更多的操作, 这样可以减少 打开和销毁 进程的一部分时间, 不过, Cpython解释器里面的线程实现不了 并行, 对于抢占CPU的利用率来说 是一个很low的东西
'''        

效率差别

数据共享:
    在进程中不存在数据共享, 如果想要共享数据,需要引用一些其他模块

    在线程中, 数据是共享的

  守护线程:

    会等待主线程结束之后才结束, 主线程会等待所有子线程结束而结束
    主线程结束, 整个进程结束, 所以 主线程需要等待所有线程结束后才能结束
    setDaemon(True) 开启守护线程.

import time
from threading import Thread
def thread1():
    while True:
        print(True)
        time.sleep(0.5)

def thread2():
    print('in t2 start')
    time.sleep(3)
    print('in t2 end')

if __name__ == '__main__':
    t1 = Thread(target=thread1)
    t1.setDaemon(True)
    t1.start()
    t2 = Thread(target=thread2)
    t2.start()
    time.sleep(1)
    print('主线程')


# 主线程如果结束了 那么整个进程就结束
# 守护线程 会等待主线程结束之后才结束.
    # 主进程 等待 守护进程 子进程
    # 守护进程 只守护主进程的代码就可以了
    # 守护线程不行 主线程如果结束了 那么整个进程就结束 所有的线程就都结束

守护线程

用类开启线程:

    继承Thread类, 在里面重写run()

from threading import Thread,get_ident

# threading.get_ident() 此方法可以用来查看线程 id
class MyThread(Thread):

    def run(self):
        print('in my thread : ',get_ident(),self.args)

print('main',get_ident())
t = MyThread('wahaha')
t.start()

用类开启线程 (不含参数)

若 方法中含有参数,  只需要在自己写的类中,重写 __init__() 构造方法, 同时继承父类的__init__() 方法:

from threading import Thread,get_ident
# 开启线程的第二种方式和查看线程id
class MyThread(Thread):
    def __init__(self,args):
        super().__init__()   # Thread类的init,在这个方法中做了很多对self的赋值操作,都是给创建线程或者使用线程的时候用的
        self.args = args

    def run(self):
        print('in my thread : ',get_ident(),self.args)

print('main',get_ident())
t = MyThread('wahaha')
t.start()

用类开启线程 (含参数)

线程中的其他方法:
    is_alive() 判断是否活着
    enumerate() 将正在进行的线程组成一个列表进行返回
    activeCount() 计算正在运行的线程的数量

 

锁Lock, 递归锁PLack

  在进程和线程中, 同时访问一个数据的时候就会产生数据不安全的现象   

  GIL 全局解释器锁:
    此锁是用来锁住解释器往CPU中传输需要计算的内容.

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

  在多线程环境中,Python 虚拟机按以下方式执行:

  a、设置 GIL;

  b、切换到一个线程去运行;

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

GIL详解

 互斥锁: Lock
    此锁是用来对内存进行锁定, 避免出现多线程 同时对某一数据进行增删改查,

    用法和进程中的锁大概也一样, 只不过在进程中的 数据本身是不被共享的, 而这里的数据本身就是共享的, 所以往往在线程中出现的概率更大.

# 尽量不要设置全局变量
# 只要在多线程/进程之间用到全局变量 就加上锁

from threading import Lock,Thread
lock = Lock()
lock.acquire()
lock.acquire()
noodle = 100
def func(name,lock):
    global noodle
    lock.acquire()
    noodle -= 1
    lock.release()
    print('%s吃到面了'%name)

if __name__ == '__main__':
    lock = Lock()  # 线程锁 互斥锁
    t_lst = []
    for i in range(10):
        t = Thread(target=func,args=(i,lock))
        t.start()
        t_lst.append(t)
    for t in t_lst:
        t.join()
    print(noodle)

Lock

递归锁: PLock
    此锁是用来解决, 在使用互斥锁的时候出现的死锁的状况

  死锁现象: 本质上还是代码的逻辑出现了问题,
    所以 递归锁只是用来进行修补的强力胶, 而不是一个正常代码中的逻辑方法

ps:
    死锁:
      多把锁同时应用在多个线程中

    互斥锁和递归锁哪个好:

      递归锁 快速恢复服务
      死锁问题的出现 是程序的设计或者逻辑的问题
      还应该进一步的排除和重构逻辑来保证使用互斥锁也不会发生死锁

    互斥锁和递归锁的区别:
      互斥锁 就是在一个线程中不能连续多次ACQUIRE
      递归锁 可以在同一个线程中acquire任意次,注意acquire多少次就需要release多少次

信号量 Semphore:
  池与信号量的区别:


     效率高:
      池子里有几个进程/线程 一共就几个进程/线程
      不管有多少任务, 池子的个数都是固定的
      开启进程和关闭进程这些事都是需要固定的时间开销
      不产生额外的时间开销
      且进程池中的进程数控制的好, 那么操作系统的压力越小

    信号量:
      有多少个任务就开多少 进程/线程
      可以帮助你进啊少操作系统切换的负担
      但是并不能帮助你减少 进程/线程的开启和关闭的时间,
      所以 信号量的效率略低

事件 Even: 同进程的事件整体来说是一样的.
  方法:
    wait() 等待, 
    set()
    clear()
    is_set()

条件:Condition
  方法:
    acquire()
    release() 
    wait() 阻塞
    notify() 让wait接触阻塞

  ps: wait() 和 notify() 必须在执行这两个方法的前后加上 acquire()和release()

定时器 Timer:

  定时器,指定n秒后执行某个操作

from threading import Timer
 
def hello():
    print("hello, world")
 
t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

 

posted @ 2018-12-19 20:30  拓荒牛wr  阅读(354)  评论(0编辑  收藏  举报