Python-多线程之谜

第一:多线程运行特点

  • 线程执行的时候是无序的
  • 主线程会等待所有的子线程执行完成以后程序再退出

第二:Python threading模块

1.直接调用处理:

'''
启动两个线程去执行run()
'''
def run(threadName):
    print(threadName,time.ctime(time.time()))

if __name__=='__main__':
    th1=threading.Thread(target=run,args=('thread-1',))
    th2=threading.Thread(target=run,args=('thread-2',))
    th1.start()
    th2.start()

'''
运行结果:
thread-1 Wed Sep 11 16:56:58 2019
thread-2 Wed Sep 11 16:56:58 2019
'''

2.继承式调用

'''
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
'''
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num=num

    def run(self):
        print(self.num, time.ctime(time.time()))
        time.sleep(2)

if __name__=='__main__':
    th1=MyThread('Thread-1')
    th2=MyThread('Thread-2')
    th3=MyThread('Thread-3')

    th1.start()
    th2.start()
    th3.start()

'''
运行结果:
Thread-1 Wed Sep 11 16:41:30 2019
Thread-2 Wed Sep 11 16:41:30 2019
Thread-3 Wed Sep 11 16:41:30 2019
'''

第三:为啥要用多线程

业务场景:·小灰灰一边玩吃鸡游戏,一边跟老婆大人聊天,两边都不能耽误啊,那怎么办?只能并行运作啊

1.并发执行两个函数(游戏聊天两不耽误)

     运行方式:先创建线程1,再创建线程2,然后去启动线程1和线程2,并和主线程同时运行,会出现以下两种情况:

  • 子线程先于主线程运行完毕,子线程要等待主线程执行完毕
  • 主线程先于子线程结束,子线程随即停止

     (天皇老子都挂了,你们这些小罗罗还忙乎个啥~~~)

'''
并行的进行游戏和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s游戏" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同时用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    th1.start()
    th2.start()
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
多次运行结果:
first:
Ryan在玩Battlegrounds游戏 Wed Sep 11 18:31:35 2019
Ryan同时用QQ在聊天 Wed Sep 11 18:31:35 2019
--------now time:Wed Sep 11 18:31:35 2019---------------
Chat stop time Wed Sep 11 18:31:37 2019
playGame stop time Wed Sep 11 18:31:37 2019
second:
Ryan在玩Battlegrounds游戏 Wed Sep 11 18:32:01 2019
--------now time:Wed Sep 11 18:32:01 2019---------------
Ryan同时用QQ在聊天 Wed Sep 11 18:32:01 2019
playGame stop time Wed Sep 11 18:32:03 2019
Chat stop time Wed Sep 11 18:32:03 2019
'''

以上运行结果分析:

每个线程都要运行2s(因为我设置了等待2s),但是最后运行结束时间都是一样的,这也证明了并发执行

2.单线程式执行两个函数(打完游戏才能聊天的方式)

可以看出下面总共执行的时间为:4s

'''
先结束游戏函数,在执行聊天函数的单线程执行方式
'''
def playGame(name,game_name):
    print("%s在玩%s游戏" %(name,game_name),time.ctime())
    time.sleep(2)

def Chat(name,chat_name):
    print('%s同时用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)


if __name__=='__main__':
    # th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    # th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    # th1.start()
    # th2.start()
    playGame("Ryan","Battlegrounds")
    Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
多次运行结果:
Ryan在玩Battlegrounds游戏 Wed Sep 11 18:06:04 2019
Ryan同时用QQ在聊天 Wed Sep 11 18:06:06 2019
--------now time:Wed Sep 11 18:06:08 2019---------------
'''

3.join(主线程等待子线程执行完成后才执行)

业务场景:做好饭你喊小灰灰吃饭(可以理解为这里的主线程),小灰灰说,决赛圈了,老婆不要打扰我啊,我要等吃鸡了再去吃饭,这时就要等待子线程结束,之后再运行主线程(也就是等他打完游戏我才能盛饭)(join()用法)

'''
并行的进行游戏和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s游戏" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同时用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    threadList=[]
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    threadList.append(th1)
    threadList.append(th2)
    for x in threadList:
        x.start()
    x.join()
    #用最后一个子线程去阻塞主线程,等待最后一个子线程执行完成再运行主线程
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
first:
Ryan在玩Battlegrounds游戏 Wed Sep 11 19:05:32 2019
Ryan同时用QQ在聊天 Wed Sep 11 19:05:32 2019
Chat stop time Wed Sep 11 19:05:34 2019
--------now time:Wed Sep 11 19:05:34 2019---------------
playGame stop time Wed Sep 11 19:05:34 2019
second:
Ryan在玩Battlegrounds游戏 Wed Sep 11 19:09:30 2019
Ryan同时用QQ在聊天 Wed Sep 11 19:09:30 2019
Chat stop timeplayGame stop time Wed Sep 11 19:09:32 2019
 Wed Sep 11 19:09:32 2019
--------now time:Wed Sep 11 19:09:32 2019---------------
'''

结果分析:

每一次都是等最后一个子线程Chat执行完成才能执行主线程

4.守护线程

业务场景:

  (1)join使用:有可能老婆等小灰灰游戏和聊天结束(他老婆是真好),开始盛饭,上菜。

(2)不限制并发执行:小灰灰游戏是结束了,但是聊天没好结束,老婆就开始盛饭,上菜,所以不管上面哪一种情况,最后都是吃完饭才洗碗(这里洗碗可以理解为主线程结束)

 其实可以看出来游戏很重要,因为要吃鸡,但是聊天无所谓,所以游戏一结束,老婆就开始盛饭,上菜,最会不管聊天是不是还在继续,我们吃完饭(主线程执行完毕)就自动挂掉

所谓’线程守护’,就是主线程不管该线程(设置为守护进程说明该进程不重要)的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭,主线程结束,那么这个进程也结束了

'''
并行的进行游戏和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s游戏" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同时用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(5)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    threadList=[]
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    # th1设置为守护进程
    # th2.setDaemon(True)
    threadList.append(th1)
    threadList.append(th2)
    for x in threadList:
        x.start()
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
1.设置th2为守护进程,运行结果:(设置th2为守护进程,故Chat才启动,还没执行完毕,程序就退出了)
Ryan在玩Battlegrounds游戏 Wed Sep 11 20:01:15 2019
Ryan同时用QQ在聊天 Wed Sep 11 20:01:15 2019
--------now time:Wed Sep 11 20:01:15 2019---------------
playGame stop time Wed Sep 11 20:01:17 2019
--------------------------------------------------
2.未设置守护进行运行结果:(主线程等待所有的子线程执行结束才退出)
Ryan在玩Battlegrounds游戏 Wed Sep 11 20:05:09 2019
Ryan同时用QQ在聊天 Wed Sep 11 20:05:09 2019--------now time:Wed Sep 11 20:05:09 2019---------------

playGame stop time Wed Sep 11 20:05:11 2019
Chat stop time Wed Sep 11 20:05:14 2019
'''

备注:setDaemon方法必须在start之前且要带一个必填的布尔型参数

第四:线程锁

1.互斥锁

业务场景:一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
(1)无同步锁情况下:

'''
减1的函数,初始赋值100,期望最终结果是0
'''
def subtraction():
    global sum
    tmp=sum
    time.sleep(0.002)
    sum=tmp-1

sum=100
if __name__ == '__main__':
    thread=[]
    #依次启动100个线程
    for x in range(100):
        t=threading.Thread(target=subtraction)
        thread.append(t)
        t.start()
    #每一个子进程执行完毕才执行主线程
    for x in thread:
        t.join()
    print('sum = ',sum)
'''
运行结果:
first:
sum =  87
---------------
second:
sum =  94
'''

运行结果解释:

我们在开启100个线程的时候,当100个线程在进行subtraction函数操作时,首先要获取各自的sum(漏洞:共同的数据不能共享同时被多线程操作)和tmp,但是此时多线程会按照时间规则来进行切换,如果当前面某些线程在处理sum时未结束,后面的进程已经开始了(增加了休眠时间来体现该效果),此时拿到的sum就不再是sum-1的期望结果了,而是拿到了sum的值。这样就会导致,此次的线程进行自减1的操作失效了。

(2)有同步锁之后:

'''
减1的函数,初始赋值100,期望最终结果是0
'''
#生成全局锁
lock = threading.Lock()
def subtraction():
    global sum
    #运行之加锁
    lock.acquire()
    tmp=sum
    time.sleep(0.002)
    sum=tmp-1
    #运行之后解锁
    lock.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次启动100个线程
    for x in range(100):
        t=threading.Thread(target=subtraction)
        thread.append(t)
        t.start()
    #每一个子进程执行完毕才执行主线程
    for x in thread:
        t.join()
    print('sum = ',sum)
'''
运行结果:
sum =  0
'''

运行结果解释:

加锁的时候要注意,需要在引起多线程相互矛盾的共同数据部分枷锁,并且加锁部分的代码就相当于单线程运行

2.信号量Semaphore

业务需求:食堂打饭,总共有5个窗口,每次最多允许5人进去挑选,有的人墨迹半天没选好,有的人很快选好出来,此时释放了一个窗口,那么阿姨就会喊再进来一个人,不管怎样,5个窗口同时运作。Semaphore就是同时允许一定数量的线程更改数据

'''
减1的函数,初始赋值100,最后sum值为随机
每次最多五个线程同时执行
'''
#生成全局锁
semaphore = threading.BoundedSemaphore(5)
def subtraction(n):
    global sum
    #运行之加锁
    semaphore.acquire()
    tmp=sum
    sum=tmp-1
    time.sleep(2)
    print("run the thread: %s\n" %n)
    # 运行之后解锁
    semaphore.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次启动24个线程
    for x in range(22):
        t=threading.Thread(target=subtraction,args=(x,))
        thread.append(t)
        t.start()
    #每一个子进程执行完毕才执行主线程
    for x in thread:
        t.join()
    print('sum = ',sum)

运行结果:

疑问:为什么每次sum值计算的都对,因为每次都应该有五个线程并发啊,还有就是打印的进程有的换行,有的没换行

疑惑解答:

真是百思不得其解,但是我还是坚持同时间内应该有五个线程同时操作啊,为什么操作结果是单线程的结果,后来我在计算之前加了一个等待时间,再运行发现发现是五个线程并发执行的结果,后来我才明白,我创建线程1,然后启动线程1,这时候再去循环创建线程2,再去启动线程2,其实我在创建线程2的时候可能线程1已经执行完了,因为实例化线程也是要时间的,所以我这边看到以为每次都是串行执行的,其实不是!

'''
减1的函数,初始赋值100,最后sum值为随机
每次最多五个线程同时执行
'''
#生成全局锁
semaphore = threading.BoundedSemaphore(5)
def subtraction(n):
    global sum
    #运行之前加锁
    semaphore.acquire()
    print("get lock: %s \n" %sum )
    #此时加等待拖延线程执行时间
    time.sleep(1)
    tmp=sum
    sum=tmp-1
    print("run the thread: %s\n" %n)
    # 运行之后解锁
    semaphore.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次启动24个线程
    for x in range(12):
        t=threading.Thread(target=subtraction,args=(x,))
        thread.append(t)
        t.start()
    #每一个子进程执行完毕才执行主线程
    for x in thread:
        t.join()
    print('sum = ',sum)
#只是加了一个线程等待时间,就证明了之前想的是对的

运行结果:

加了等待时间会发现,第一次进来的五个线程,共享的sum值都是100

第五:线程中队列

业务场景:某面包店推出营销策略,说每天早上的第一个蛋糕打3折,那么好多人早上都去抢购那第一个蛋糕

1.多线程的方式去删除列表中的第一个元素(抢购第一个蛋糕)

'''
用多线程删除表中的第一个元素
'''
list = ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
def MyRemove(x):
    #获取列表第一个个元素
    sale = x[0]
    print('被删除的元素为:',sale)
    time.sleep(0.001)
    x.remove(sale)


if __name__ == '__main__':
    th = []
    for i in range(3):
        t = threading.Thread(target=MyRemove, args=(list,))
        th.append(t)
        print('当前线程为:',t.getName())
        t.start()
    for i in th:
        i.join()
    print('没有被删除的列表元素为:', list)

'''
当前线程为: Thread-1
被删除的元素为: bread1当前线程为: 
Thread-2
被删除的元素为: bread1
当前线程为: Thread-3
被删除的元素为: bread1
Traceback (most recent call last):
  File "D:/tests/th.py", line 25, in <module>
    x.join()
NameError: name 'x' is not defined
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python28\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Python28\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:/tests/th.py", line 14, in MyRemove
    x.remove(sale)
ValueError: list.remove(x): x not in list

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python28\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Python28\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:/tests/th.py", line 14, in MyRemove
    x.remove(sale)
ValueError: list.remove(x): x not in list

'''

运行结果分析:可以看到线程1和线程2都报异常,因为他们去删除这个元素的时候,该元素已经被线程3删除了,故他们找不到这个元素,所以发生异常了,这也体现了线程无序并发执行,不过可以用线程锁去实现,

2.Queue线程队列

Queue运行的三大特征:

  • 先进先出(FIFO)

         Q=queue.Queue()
         Q.put(maxsize)

  • 先进后出(LIFO)

        Q=queue.LifoQueue()
        Q.put(maxsize)

  • 按照优先级进出

        Q = queue.PriorityQueue()

       Q.put(list)list长度至少为2,第一个元素表示优先级,第二个元素表示存放的对应的值,如:Q.put([]1,23)

      2.1:先进先出的原则去操作列表(相当于线程锁,不顾这里执行的线程顺序一定是12345。。。这样,但是线程中互斥锁就不一定了,只是说同时间只能有一个线程在执行,只有等该线程执行完成之后,才能释放锁给其他线程操作)

'''
用队列的方式依次去删除列表的第一个元素
'''
list = ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
def MyRemove(x):
    #获取列表第一个个元素
    sale = x[0]
    print('被删除的元素为:',sale)
    time.sleep(0.001)
    x.remove(sale)


if __name__ == '__main__':
    print('初始化队列元素为:',list)
    Q = queue.Queue()
    for i in range(3):
        t = threading.Thread(target=MyRemove, args=(list,))
        #每次创建一个线程都加到队列Q里面
        Q.put(t)
    while not Q.empty():
        run_th = Q.get()
        print('running thread:',run_th.getName())
        run_th.run()
    print('没有被删除的列表元素为:', list)

'''
初始化队列元素为: ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
running thread: Thread-1
被删除的元素为: bread1
running thread: Thread-2
被删除的元素为: bread2
running thread: Thread-3
被删除的元素为: bread3
没有被删除的列表元素为: ['bread4', 'bread5']
'''

      2.2:先进后出的方式执行

结果可以看出线程3是最后进到队列里面的,但是确实最先执行的

初始化队列元素为: ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
running thread: Thread-3
被删除的元素为: bread1
running thread: Thread-2
被删除的元素为: bread2
running thread: Thread-1
被删除的元素为: bread3
没有被删除的列表元素为: ['bread4', 'bread5']

      2.3:优先级执行

if __name__=='__main__':
    Q = queue.PriorityQueue()
    Q.put([4, 'zhu'])
    Q.put([8, 'xiao'])
    Q.put([1, 'pi'])

    while not Q.empty():
        run_th = Q.get()
        print('最后结果:',run_th)
'''
最后结果: [1, 'pi']
最后结果: [4, 'zhu']
最后结果: [8, 'xiao']
'''

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值