并发编程之线程(代码)

一、threading模块介绍

multiprocessor模块完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,可查看官网。

官网链接:https://docs.python.org/3/library/threading.html?highlight=threading#

二、开启线程的两种方式

  方式一:

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print("%s say hello"%name)

if __name__ == '__main__':
    t = Thread(target=sayhi,args=('张无忌',))
    t.start()
    print("主线程")

  方式二:

from threading import Thread
import time
class Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self):
        time.sleep(2)
        print("%s say hello"%self.name)
        
if __name__ == '__main__':
    t = Sayhi("张无忌")
    t.start()
    print("主线程")

 

 

三、在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

   1、比较谁开启的速度快

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    # 在主线程线开启线程
    t = Thread(target=work)
    t.start()
    print("主线程/主进程")
    '''
    结果:
    hello
    主线程/主进程
    '''

    #在主进程小开启子进程
    p = Process(target=work)
    p.start()
    print("主线程/主进程")
    '''
     结果:
     主线程/主进程
     hello
     '''

  2、查看pid

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello:',os.getpid())

if __name__ == '__main__':
    # part1:在主进程下开启多个线程,每个线程都跟主线程的pid一样
    t1 = Thread(target=work)
    t2 = Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid:',os.getpid())

    #part2:开多个进程,每个进程都有不同的pid
    p1 = Process(target=work)
    p2 = Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程:',os.getpid())

  3、同一进程内的线程共享该进程的数据?

from threading import Thread
from multiprocessing import Process
import os

def work():
    global n
    n = 0

if __name__ == '__main__':
    n = 100
    p = Process(target=work)
    p.start()
    p.join()
    print("",n)
    #子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然是100
    
    n = 1
    t = Thread(target=work)
    t.start()
    t.join()
    print("",n)
    #查看结果为0,因为同一进程内的线程之间共享进程内的数据

 

 

四、线程相关的其他方法

  Thread实例对象的方法:

    isAlive():返回线程是否活动的

    getName():返回线程名

    setName():设置线程名

  threading模块提供的一些方法:

    threading.currentTherad():返回当前的线程变量

    threading.enumerate():返回一个包含正在运行的线程list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

    threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 

from threading import Thread
import threading
from multiprocessing import Process
import os
import time
def work():
    time.sleep(3)
    print(threading.current_thread().getName())

if __name__ == '__main__':
    #在主进程下开启线程
    t = Thread(target=work)
    t.start()

    print(threading.current_thread().getName())
    print(threading.current_thread())  #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程
    print('运行中的线程:',threading.active_count())
    print("主线程/主进程")

结果:
'''
MainThread
<_MainThread(MainThread, started 17188)>
[<_MainThread(MainThread, started 17188)>, <Thread(Thread-1, started 16996)>]
运行中的线程: 2
主线程/主进程
Thread-1
'''

 

  主线程等待子线程结束

from threading import Thread
import time

def sayhi(name):
    time.sleep(2)
    print("%s say hello"%name)

if __name__ == '__main__':
    t = Thread(target=sayhi,args=("张无忌",))
    t.start()
    t.join()
    print("主线程")
    print(t.is_alive())

结果:
'''
张无忌 say hello
主线程
False
'''

 

五、守护线程

  无论是进程还是线程,都遵循:守护XXX会等待主XXX运行完毕后被销毁。

  注意:运行完毕并非终止运行。

1、对主进程来说,运行完毕指的是主进程代码运行完毕
    解释:主进程在其代码结束后就已经算运行完毕了(守护进程在此时被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。


2、对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
    解释:主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread
import time
def sayhi(name):
    time.sleep(3)
    print("%s say hollo"%name)

if __name__ == '__main__':
    t = Thread(target=sayhi,args=("张无忌",))
    t.setDaemon(True)   #必须在t.start()之前设置
    t.start()

    print("主线程")
    print(t.is_alive())

结果:
'''
主线程
True
'''

 

例子:

from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")

if __name__ == '__main__':

    t1 = Thread(target=foo)
    t2 = Thread(target=bar)

    t1.daemon = True
    t1.start()
    t2.start()
    print("main...................")

 

六、Python GIL(Global Interpreter Lock)解释器全局锁

  1、GIL介绍:

    GIL本质就是一把互斥锁,既然是互斥锁,所有的互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

    可以肯定的是:保护不同的数据的安全,就应该加不同的锁。

    要想了解GIL,首先确定一点的是每次执行python程序,都会产生一个独立的进程。例如python test.py、python aaa.py 、python bbb.py 会产生3个不同的进程

    在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有的线程都运行在这一个进程内。

1、所有的数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有的代码以及Cpython解释器的所有代码)
    例如:test.py定义了一个函数work,在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是而可以执行。
2、所有的线程的任务,都需要将任务的代码当作参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

综上:
  如果多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行,解释器的代码是所有线程共享的,
所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:
  对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没什么高明的方法,就是加锁处理
如下图的GIL,保证python解释器同一时间只能执行一个任务代码

   2、GIL与Lock

    GIL保护的是解释器级别的数据,保护用户自己的数据则需要自己加锁处理,如图:

  3、GIL与多线程

    有了GIL的存在,同一时刻同一进程只有一个线程被执行

    问:cpu到底是用来做计算的还是用来做I/O的?

      多个cpu,意味着可以有多个核并完成计算,所以多核提升的是计算性能

      每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用。

    总结:

      对计算来所,cpu越多越好,但是对于I/O来说,再多的cpu也没用。

      当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程的应用场景。

#分析
有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核情况下,分析结果:
    如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,不适合方案二
    如果四个任务是I/O密集型,方案一创建进程开销大,且进程的切换速度远远不如线程,不适合方案二

#多核情况下,分析结果:
    如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程同一时刻只有一个线程执行用不上多核,不适合方案一
    如果四个任务是I/O密集型,再多的核也解决不了I/O问题,不适合方案一

结论:
    现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于I/O密集型的任务效率还是有显著提升的。

  4、多线程性能测试

    计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res = 0
    for i in range(100000000):
        res *= i

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机是八核
    start = time.time()
    for i in range(4):
        p = Process(target=work)  #耗时5.946232795715332秒
        t = Thread(target=work)   ##耗时15.780431747436523秒
        # l.append(p)
        l.append(t)
        # p.start()
        t.start()

    # for p in l:
    #     p.join()
    for t in l:
        t.join()

    stop = time.time()
    print("run time is %s"%(stop - start))

 

    I/O密集型:多线程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print(">>>>>>>>>>")

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  #本机是8核
    start = time.time()
    for i in range(400):
        p = Process(target=work)  #耗时11.058857917785645秒
        t = Thread(target=work)   #耗时2.0456159114837646秒
        # l.append(p)
        l.append(t)
        # p.start()
        t.start()
    # for p in l:
    #     p.join()
    for t in l:
        t.join()
    stop = time.time()
    print('run tiem is %s'%(stop - start))

  应用:

    多线程用于I/O密集型,如socket、爬虫、web

    多进程用于计算密集型,如金融分析

七、同步锁

 

八、死锁现象和递归锁

九、信号量Semaphore

十、Event

十一、条件Condition(了解)

十二、定时器

十三、线程queue

十四、Python标准模块--concurrent.futres

转载于:https://www.cnblogs.com/LW-5208/articles/9871948.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值