python进阶--多线程

文章目录

      • 多线程 vs 多进程
      • 同步/异步/并发/并行
      • python多线程
        • _thread包的使用
        • threading的使用
        • 守护线程-daemon
        • 线程常用属性
        • 继承方法使用多线程
      • 多线程共享全局变量
        • 线程非安全
        • 线程互斥锁
        • 线程死锁
        • 可重入锁
      • threading模块拓展
        • threading.Semaphore()
        • threading.Timer
      • 全局解释器锁(GIL)

多线程 vs 多进程

  • 程序:一堆代码以文本形式存入一个文档
  • 进程: 程序运行的一个状态
    - 包含地址空间,内存,数据栈等
    - 每个进程由自己完全独立的运行环境,多进程共享数据是一个问题
  • 线程
    - 一个进程的独立运行片段,一个进程可以由多个线程
    - 轻量化的进程
    - 一个进程的多个线程间共享数据和上下文运行环境
    - 共享互斥问题
  • 全局解释器锁(GIL)
    - Python代码的执行是由python虚拟机进行控制
    - 在主循环中只能有一个控制线程在执行
    - 参考文档:https://www.cnblogs.com/cjaaron/p/9166538.html

同步/异步/并发/并行

概念

  • 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication).
  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了.
  • 所谓异步,则是调用在发出之后,这个调用就直接返回了,所以没有返回结果。后续被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.
  • 参考文档:https://www.cnblogs.com/mhq-martin/p/9035640.html
  • 并发和并行:https://blog.csdn.net/weixin_39003229/article/details/80681024

python多线程

_thread包的使用

thread:有问题,不好用
_thread:python3改成了_thread
threading: 通行的包

# _thread多线程,缩短总时间
import time
import _thread as thread

def loop1(in1):
    # ctime 得到当前时间
    print('Start loop 1 at :', time.ctime())
    # 把参数打印出来
    print("我是参数 ",in1)
    # 睡眠多长时间,单位是秒
    time.sleep(4)
    print('End loop 1 at:', time.ctime())

def loop2(in1, in2):
    # ctime 得到当前时间
    print('Start loop 2 at :', time.ctime())
    # 把参数in 和 in2打印出来,代表使用
    print("我是参数 " ,in1 , "和参数  ", in2)
    # 睡眠多长时间,单位是秒
    time.sleep(2)
    print('End loop 2 at:', time.ctime())

def main():
    print("Starting at:", time.ctime())
    # 参数两个,一个是需要运行的函数名,第二是函数的参数作为元祖使用,为空则使用空元祖 
    # 注意:如果函数只有一个参数,需要参数后由一个逗号
    thread.start_new_thread(loop1,("王老大", ))
    thread.start_new_thread(loop2,("王大鹏", "王晓鹏"))
    print("All done at:", time.ctime())

if __name__ == "__main__":
    main()
    # 一定要有while语句
    # 因为启动多线程后本程序就作为主线程存在,如果主线程执行完毕,则子线程可能也需要终止  
    while True:
        time.sleep(10)

>Starting at: Thu Aug 23 21:52:50 2018
 All done at: Wed Aug 22 22:24:40 2018
 Start loop 1 at : Wed Aug 22 22:24:40 2018
 我是参数  王老大
 Start loop 2 at : Wed Aug 22 22:24:40 2018
 我是参数  王大鹏 和参数   王晓鹏
 End loop 2 at: Wed Aug 22 22:24:42 2018
 End loop 1 at: Wed Aug 22 22:24:44 2018
threading的使用
直接利用threading.Thread生成Thread实例
语法:
    t = threading.Thread(target=xxx, args=(xxx,))
    t.start():启动多线程
    t.join(): 主线程等待多线程执行完成

无join()方法时,子线程启动后,程序会将资源返回给主程序。

import threading
import time

def func1():
    time.sleep(2)
    print(func1)
    time.sleep(2)   
    print(func1)

def func2():
    time.sleep(3)
    print(func2)


if __name__ == "__main__":
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()
    # t1.join()
    # t2.join()
    print(threading.activeCount())
    print('运行结束')
    print(threading.activeCount())


print(threading.activeCount())

>
3
运行结束
3
3
<function func1 at 0x7ffb59db9160>
<function func2 at 0x7ffb5aae2670>

join():阻塞当前进程/线程,直到调用join方法的那个进程执行完,再继续执行当前进程


if __name__ == "__main__":
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(threading.activeCount())
    print('运行结束')
    print(threading.activeCount())


print(threading.activeCount())
>
<function func1 at 0x7fdacf6228b0>
<function func2 at 0x7fdacf6d8700>
<function func1 at 0x7fdacf6228b0>
1
运行结束
1
1
守护线程-daemon
在程序中将子线程设置成守护现成,则子线程会在主线程结束的时候自动退出
一般认为,守护线程不重要或者不允许离开主线程独立运行
守护线程案例能否有效果跟环境相关
# 非守护线程
import time as t
import threading

def fun():
    print('start fun')
    t.sleep(2)
    print('end fun')

print('main thread')

## 注意:此处函数不带括号
t1 = threading.Thread(target=fun, args=())
t1.start()
t.sleep(1)
print('main thread end')

>main thread
 start fun
 main thread end
 end fun
 ## 从结果看出:主程序结束后,线程并未结束
# 守护线程
import time
import threading

def fun():
    print("Start fun")
    time.sleep(2)
    print("end fun")

print("Main thread")

t1 = threading.Thread(target=fun, args=() )
# 社会守护线程的方法,必须在start之前设置,否则无效
t1.setDaemon(True)
# 设置线程的第二种方式
#t1.daemon = True
t1.start()

time.sleep(1)
print("Main thread end")

>Main thread
 Start fun
 Main thread end
 # 主线程结束后,守护线程也结束
线程常用属性
threading.currentThread:返回当前线程变量
threading.enumerate:返回一个包含正在运行的线程的list,正在运行的线程指的是线程启动后,结束前的状态
threading.activeCount: 返回正在运行的线程数量,效果跟 len(threading.enumerate)相同
threading.setName: 给线程设置名字
threading.getName: 得到线程的名字
import time
import threading

def loop1():
    print('Start loop 1 at :', time.ctime())
    time.sleep(6)
    print('End loop 1 at:', time.ctime())

def loop2():
    print('Start loop 2 at :', time.ctime())
    time.sleep(1)
    print('End loop 2 at:', time.ctime())

def loop3():
    print('Start loop 3 at :', time.ctime())
    time.sleep(5)
    print('End loop 3 at:', time.ctime())

def main():
    print("Starting at:", time.ctime())
    t1 = threading.Thread(target=loop1, args=( ))
    # threading.setName: 给线程设置名字
    t1.setName("THR_1")
    t1.start()

    t2 = threading.Thread(target=loop2, args=( ))
    t2.setName("THR_2")
    t2.start()

    t3 = threading.Thread(target=loop3, args=( ))
    t3.setName("THR_3")
    t3.start()

    # 3秒后,thread2已经自动结束,
    time.sleep(3)
    # enumerate 得到正在运行子线程,即子线程1和子线程3
    for thr in threading.enumerate():
        # getName能够得到线程的名字
        print("正在运行的线程名字是: {0}".format(thr.getName()))
        # threading.activeCount: 返回正在运行的线程数量,效果跟 len(threading.enumerate)相同
    print("正在运行的子线程数量为:{0}".format(threading.activeCount()))

    print("All done at:", time.ctime())

if __name__ == "__main__":
    main()
    # 一定要有while语句
    # 因为启动多线程后本程序就作为主线程存在 # 如果主线程执行完毕,则子线程可能也需要终止  
     while True:
        time.sleep(10)

>Starting at: Thu Aug 23 22:19:20 2018
 Start loop 1 at : Thu Aug 23 22:19:20 2018
 Start loop 2 at : Thu Aug 23 22:19:20 2018
 Start loop 3 at : Thu Aug 23 22:19:20 2018
 End loop 2 at: Thu Aug 23 22:19:21 2018
 正在运行的线程名字是: MainThread
 正在运行的线程名字是: THR_1
 正在运行的线程名字是: THR_3
 正在运行的子线程数量为:3
 All done at: Thu Aug 23 22:19:23 2018
 End loop 3 at: Thu Aug 23 22:19:25 2018
 End loop 1 at: Thu Aug 23 22:19:26 2018
继承方法使用多线程

以上均为直接生成Thread的实例,现在采用第二种直接继承自threading.Thread

语法:
    - 直接继承Thread
    - 重写run函数
    - 类实例可以直接运行
import threading
import time

# 1. 类需要继承自threading.Thread
class MyThread(threading.Thread):
    def __init__(self, arg):
        super(MyThread, self).__init__()
        self.arg = arg

    # 2 必须重写run函数,run函数代表的是真正执行的功能
    def run(self):
        time.sleep(2)
        print("The args for this class is {0}".format(self.arg))

for i in range(5):
    t = MyThread(i)
    t.start()
    t.join()

print("Main thread is done!!!!!!!!")

>The args for this class is 0
 The args for this class is 1
 The args for this class is 2
 The args for this class is 3
 The args for this class is 4
 Main thread is done!!!!!!!!

企业多线程写法

import threading
from time import sleep, ctime

class ThreadFunc:

    def __init__(self, name):
        self.name = name

    def loop(self, nloop, nsec):
        """
        :param nloop: loop函数的名称
        :param nsec: 系统休眠时间
        :return:
        """
        print('Start loop ', nloop, 'at ', ctime())
        sleep(nsec)
        print('Done loop ', nloop, ' at ', ctime())

def main():
    print("Starting at: ", ctime())

    # 以下t1 和  t2的定义方式相等
    t = ThreadFunc("loop")
    t1 = threading.Thread(target=t.loop, args=("LOOP1", 4))
    # 下面这种写法更西方人,工业化一点
    t2 = threading.Thread(target=ThreadFunc('loop').loop, args=("LOOP2", 2))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("ALL done at: ", ctime())

if __name__ == '__main__':
    main()

>Starting at:  Thu Aug 23 22:55:49 2018
 Start loop  LOOP1 at  Thu Aug 23 22:55:49 2018
 Start loop  LOOP2 at  Thu Aug 23 22:55:49 2018
 Done loop  LOOP2  at  Thu Aug 23 22:55:51 2018
 Done loop  LOOP1  at  Thu Aug 23 22:55:53 2018
 ALL done at:  Thu Aug 23 22:55:53 2018

多线程共享全局变量

线程非安全

共享变量: 当多个线程同时访问全局变量的时候,可能造成多线程之间对全局变量的混乱(即线程非安全)

import threading

sum = 0
n = 1000000

def Add():
    global sum, n
    for i in range(n):
        sum += 1

def Minu():
    global sum, n
    for i in range(n):
        sum -= 1

if __name__ == '__main__':
    print('Starting......{0}'.format(sum))
    t1 = threading.Thread(target=Add, args=())
    t2 = threading.Thread(target=Minu, args=())
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('End......{0}'.format(sum))


>Starting......0
 End......-182314
线程互斥锁

共享变量问题解决方法:锁(Lock)—是一个标志,表示一个线程在占用一些资源
步骤:上锁–>使用共享资源–>取消锁,释放锁

import threading

sum = 0
n = 1000000

lock =threading.Lock()

def Add():
    global sum, n
    for i in range(n):
        ## 上锁,申请锁
        lock.acquire()
        sum += 1
        ## 释放锁
        lock.release()

def Minu():
    global sum, n
    for i in range(n):
        lock.acquire()
        sum -= 1
        lock.release()

if __name__ == '__main__':
    print('Starting......{0}'.format(sum))
    t1 = threading.Thread(target=Add, args=())
    t2 = threading.Thread(target=Minu, args=())
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('End......{0}'.format(sum))

>Starting......0
 End......0
线程死锁

当存在多把锁时,容易造成死锁问题,因此使用python锁时需要谨慎

import threading
import time

lock_1 = threading.Lock()
lock_2 = threading.Lock()

def func_1():

   print("func_1 starting.........")
   lock_1.acquire()
   print("func_1 申请了 lock_1....")
   time.sleep(2)
   print("func_1 等待 lock_2.......")
   lock_2.acquire()
   print("func_1 申请了 lock_2.......")

   lock_2.release()
   lock_1.release()


def func_2():
   print("func_2 starting.........")
   lock_2.acquire()
   print("func_2 申请了 lock_2....")
   time.sleep(4)
   print("func_2 等待 lock_1.......")
   lock_1.acquire()
   print("func_2 申请了 lock_1.......")

   lock_1.release()
   lock_2.release()

if __name__ == "__main__":

   print("主程序启动..............")
   t1 = threading.Thread(target=func_1, args=())
   t2 = threading.Thread(target=func_2, args=())
   t1.start()
   t2.start()
   t1.join()
   t2.join()

>主程序启动......
 函数1没有申请到锁2,会释放锁1
 函数2申请到了锁1
 主程序完成......

可重入锁
  • 一个锁,可以被一个线程多次申请
  • 主要解决递归调用的时候,需要申请锁的情况
import threading
import time

class MyThread(threading.Thread):
    ## 重写run函数
  def run(self):
        global num
        time.sleep(1)

        if mutex.acquire(1):
            num = num+1
            msg = self.name +' set num to '+str(num)
            print(msg)
            mutex.acquire()
            mutex.release()
            mutex.release()

num = 0
## 实例化可重入锁
mutex = threading.RLock()

def testTh():
    for i in range(5):
        t = MyThread()
        t.start()

threading模块拓展

threading.Semaphore()

设置一个资源最多由几个多线程同时使用,超出的新线程需要等待资源

import threading
import time

# 参数定义最多几个线程同时使用资源
semaphore = threading.Semaphore(3)

def func():
    if semaphore.acquire():
        print(threading.currentThread().getName() + ' get semaphore')
        time.sleep(2)
        semaphore.release()
        print(threading.currentThread().getName() + ' release semaphore')

for i in range(8):
    t1 = threading.Thread(target=func)
    t1.start()
threading.Timer

threading.Timer–利用多线程,在指定时间后启动一个功能

import threading
import time

def func():
    print("I am running.........")
    time.sleep(4)
    print("I am done......")

if __name__ == "__main__":
    t = threading.Timer(6, func)
    t.start()

    i = 0
    while True:
        print("{0}***************".format(i))
        time.sleep(3)
        i += 1

全局解释器锁(GIL)

  • python语言和GIL半毛钱关系都没有,是由于Cpython解释器的历史原因导致。
  • GIL:全局解释器锁。每个线程在执行的过程中都要先获取GIL,保证同时只有一个线程可以执行代码。因此python多线程只能使用一个CPU,无法有效利用多核CPU资源
  • 特例:python写文件时由于不需要python解释器的支持,因此可以实现并发。
  • IO密集型:涉及到大量网络、磁盘IO的任务,特点是CPU消耗低,大量时间浪费在IO等待,建议使用多线程
  • CPU密集型:涉及到大量计算的任务,特点是CPU消耗高,不建议使用多线程

你可能感兴趣的:(python-learn,python)