python 多线程threading lock condition Queen

提高效率

一个人干活没有两个人干活快,也就是说多线程是为了更好的帮助程序执行提高效率。

python多线程

总体来说,在python中,实际上并不提倡使用多线程来提高效率,或是说不提倡在CPython中使用多线程,这是因为在python设计之初的时候,那个年代只有单核cpu,由于硬件发展速度过快,最初设计的为了保护数据的一种机制,GIL(global interpreter lock),简单来说就是在python程序执行的开始,底层会锁住这段程序,以防止数据的混乱,关门,不让其他程序参与进来,这种机制在硬件发展飞速的今天,成为了一个遗留问题。

  • Cpython应对这种机制办法:
  1. 使用多进程 mutiprocess模块,可以解决,比较吃cpu资源
  2. 使用其他语言编写多线程模块(c语言等),调用api
  3. 分布式架构,使用多个虚拟环境同时干活(Docker)
  4. 舍弃Cpython解释器,使用IornPython或是JavaPython
  • cpython多线程使用注意事项
  1. 在使用cpu密集型的代码时,不建议使用,建议多进程
  2. 在IO密集型的代码时,还是可以提高一点效率的。

示例

不使用多线程程序

import time


def func(n):
    print("函数开始执行", time.ctime())
    time.sleep(n)
    print("函数执行结束", time.ctime())


def main():
    print("主函数开始执行", time.ctime())
    func(2)
    func(3)
    print("主函数执行结束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函数开始执行 Tue Oct 29 13:34:08 2019
函数开始执行 Tue Oct 29 13:34:08 2019
函数执行结束 Tue Oct 29 13:34:10 2019
函数开始执行 Tue Oct 29 13:34:10 2019
函数执行结束 Tue Oct 29 13:34:13 2019
主函数执行结束 Tue Oct 29 13:34:13 2019
"""

"""
在本次程序中,代码从上到下依次执行,func()第一次执行完毕后,第二次才开始执行
所以本次程序一共花费了5秒
"""

使用多线程

import time
import threading

def func(n):
    print(f"{threading.current_thread().name}-函数开始执行", time.ctime())
    time.sleep(n)
    print(f"{threading.current_thread().name}函数执行结束", time.ctime())


def main():
    print("主函数开始执行", time.ctime())
    t1 = threading.Thread(target=func, args=(3,))
    t2 = threading.Thread(target=func, args=(2,))
    t1.start()
    t2.start()
    print("主函数执行结束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函数开始执行 Tue Oct 29 13:55:41 2019
Thread-1-函数开始执行 Tue Oct 29 13:55:41 2019
Thread-2-函数开始执行 Tue Oct 29 13:55:41 2019
主函数执行结束 Tue Oct 29 13:55:41 2019
Thread-2函数执行结束 Tue Oct 29 13:55:43 2019
Thread-1函数执行结束 Tue Oct 29 13:55:44 2019
"""

"""
本次程序主函数执行花费了0秒,主函数都结束了,但是线程还没有结束,
这显然不符合我们的预期,所以,这时候我们需要主函数等待线程执行完毕以后,
在继续执行。所以就用到了join()方法。
"""

join()

import time
import threading

def func(n):
    print(f"{threading.current_thread().name}-函数开始执行", time.ctime())
    time.sleep(n)
    print(f"{threading.current_thread().name}函数执行结束", time.ctime())


def main():
    print("主函数开始执行", time.ctime())
    t1 = threading.Thread(target=func, args=(3,))
    t2 = threading.Thread(target=func, args=(2,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主函数执行结束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函数开始执行 Tue Oct 29 13:58:33 2019
Thread-1-函数开始执行 Tue Oct 29 13:58:33 2019
Thread-2-函数开始执行 Tue Oct 29 13:58:33 2019
Thread-2函数执行结束 Tue Oct 29 13:58:35 2019
Thread-1函数执行结束 Tue Oct 29 13:58:36 2019
主函数执行结束 Tue Oct 29 13:58:36 2019
"""

"""
本次程序主函数执行花费了3秒,线程1花费了3秒,线程2花费了3秒
得出线程1与线程2是同时执行的。

注意:相比于不使用多线程的时候,省了两秒的时间。
"""

lock()

在上面程序中我们只是看到了时间确实是省了下来,但是因为并没有涉及到数据,因此我们现在测试,多线程对数据的影响,尽量不要让线程对全局变量操作。

不上锁的情况

因为我们是使用随机数来模拟线程的执行,所以不能考虑执行效率

import random
import time
import threading

"""
我们现在使用两个进程操作同一个数据元素,比如列表,我们希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一个进程则会添加两遍,但是为了效率,我们使用两个线程同时添加,我们使用time.sleep来模拟线程的动作,
比如说有些线程抢占的比较快,有些抢占比较慢。

"""

l1 = []
lock = threading.Lock()
def func(l):

    with lock:
        for i in range(5):
            time.sleep(random.randint(0,2))
            l.append(i)

def main():
    print("主函数开始执行", time.ctime())
    t1 = threading.Thread(target=func, args=(l1,))
    t2 = threading.Thread(target=func, args=(l1,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(l1)
    print("主函数执行结束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函数开始执行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函数执行结束 Tue Oct 29 14:28:04 2019

"""

"""
从上面可以看出,出现的结果比不是我们想要的【0,1,2,3,4,0,1,2,3,4】
这就是因为线程的执行顺序是无序的。所以门要想控制他们的顺序得到我们想要的结果,
这时候lock()就上场了。
"""

上锁的情况

import random
import time
import threading

"""
我们现在使用两个进程操作同一个数据元素,比如列表,我们希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一个进程则会添加两遍,但是为了效率,我们使用两个线程同时添加,我们使用time.sleep来模拟线程的动作,
比如说有些线程抢占的比较快,有些抢占比较慢。

"""

l1 = []
lock = threading.Lock()   # 创建锁对象
def func(l):
    with lock:
        for i in range(5):
            time.sleep(random.randint(0,2))
            l.append(i)

def main():
    print("主函数开始执行", time.ctime())
    t1 = threading.Thread(target=func, args=(l1,))
    t2 = threading.Thread(target=func, args=(l1,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(l1)
    print("主函数执行结束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函数开始执行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函数执行结束 Tue Oct 29 14:28:04 2019

"""

"""
这次我们可以看出输出符合我们的预期
"""

生产者消费者模式

import random
import threading
import time

gmoney = 1000    # 定义总钱数
lock = threading.Lock()  # 创建锁对象
gTotalTimes = 10    # 生产总次数
gTime = 0           # 记录生产次数

class Producer(threading.Thread):
    def run(self):
        global gmoney
        global gTime
        while True:
            with lock:
                money = random.randint(100, 1000)
                if gTime >= 10:         # 当生产了10次,结束程序
                    break
                gmoney += money
                print(f"{threading.current_thread().name}生产了{money},总剩余{gmoney}")
                gTime += 1
                time.sleep(1)
                
class Consumer(threading.Thread):
    def run(self):
        global gmoney
        while True:
            with lock:
                money = random.randint(100, 1000)
                if gmoney >= money:
                    gmoney -= money
                    print(f"{threading.current_thread().name}消费了{money},总剩余{gmoney}")
                    time.sleep(1)
                else:
                    if gTime >= gTotalTimes:   # 当生产者不生产的时候,结束
                        break
                    
def main():
    for x in range(3):
        t = Consumer(name=f"消费者线程{x}")
        t.start()

    for x in range(5):
        t = Producer(name=f"生产者线程{x}")
        t.start()


if __name__ == '__main__':
    main()
    
"""
消费者线程0消费了629,总剩余371
消费者线程0消费了138,总剩余233
消费者线程0消费了188,总剩余45
消费者线程0消费了351,没钱了
消费者线程0消费了390,没钱了
消费者线程2消费了722,没钱了
消费者线程2消费了572,没钱了
生产者线程1生产了462,总剩余507
生产者线程1生产了695,总剩余1202
生产者线程1生产了333,总剩余1535
生产者线程4生产了834,总剩余2369
生产者线程4生产了244,总剩余2613
生产者线程4生产了606,总剩余3219
生产者线程4生产了592,总剩余3811
生产者线程4生产了277,总剩余4088
生产者线程4生产了422,总剩余4510
生产者线程4生产了345,总剩余4855
消费者线程1消费了925,总剩余3930
消费者线程1消费了844,总剩余3086
消费者线程1消费了967,总剩余2119
消费者线程1消费了277,总剩余1842
消费者线程1消费了808,总剩余1034
消费者线程1消费了878,总剩余156
消费者线程1消费了117,总剩余39
"""

condition模式生产者消费者模式

lock上锁解锁比较消耗cpu资源,condition就是优化这种模式的
threading.Condition()继承于threading.Lock()
常用函数说明:

  • acquire:上锁
  • release:解锁
  • wait:将当前线程处于等待状态,并且会释放锁,可以被其他线程使用notify和notify_all函数唤醒,被唤醒后,会继续等待上锁,上锁后继续执行下面的代码
  • notify:通知某个正在等待的线程,默认是第一个等待的线程
  • notify_all:通知所有正在等待的线程,notify和notify_all不会释放锁,并且需要在release之前调用。
"""
生产者与消费者操作同一个全局变量,gmoney,生产者生产,消费者消费,
当gmoney不足时,就挂起(wait()),等待生产者生产,
当生产者生产完毕以后,就通知(notify_all())消费者可以继续消费。
相比于LOCK()频繁上锁,更节省CPU资源
"""
import random
import threading
import time

gmoney = 1000
condition = threading.Condition()
gTotalTimes = 10
gTime = 0



class Producer(threading.Thread):
    def run(self):
        global gmoney
        global gTime
        while True:
            money = random.randint(100, 1000)
            condition.acquire()
            if gTime >= gTotalTimes:
                condition.release()
                break
            gmoney += money
            print(f"{threading.current_thread().name}生产了{money},总剩余{gmoney}")
            gTime += 1
            condition.notify_all()
            condition.release()
            time.sleep(1)



class Consumer(threading.Thread):
    def run(self):
        global gmoney
        while True:
            money = random.randint(100, 1000)
            condition.acquire()
            while gmoney <= money:
                if gTime >= gTotalTimes:
                    condition.release()
                    return
                print(f"{threading.current_thread().name}准备消费{money},不足")
                condition.wait()
            gmoney -= money
            print(f"{threading.current_thread().name}消费了{money},剩余{gmoney}")

            condition.release()
            time.sleep(1)



def main():
    for x in range(3):
        t = Consumer(name=f"消费者线程{x}")
        t.start()

    for x in range(5):
        t = Producer(name=f"生产者线程{x}")
        t.start()


if __name__ == '__main__':
    main()

"""
消费者线程0消费了100,剩余900
消费者线程1消费了667,剩余233
消费者线程2准备消费368,不足
生产者线程0生产了981,总剩余1214
生产者线程1生产了526,总剩余1740
消费者线程2消费了368,剩余1372
生产者线程2生产了777,总剩余2149
生产者线程3生产了915,总剩余3064
生产者线程4生产了925,总剩余3989
消费者线程0消费了453,剩余3536
消费者线程1消费了982,剩余2554
生产者线程1生产了893,总剩余3447
生产者线程2生产了492,总剩余3939
生产者线程0生产了522,总剩余4461
消费者线程2消费了801,剩余3660
生产者线程3生产了562,总剩余4222
生产者线程4生产了500,总剩余4722
消费者线程1消费了889,剩余3833
消费者线程0消费了693,剩余3140
消费者线程2消费了423,剩余2717
消费者线程0消费了876,剩余1841
消费者线程1消费了559,剩余1282
消费者线程2消费了674,剩余608
消费者线程1消费了592,剩余16
"""

Queen

线程安全队列
常用函数说明:

  • Queen(maxsize):创建一个先进先出的队列
  • qsize():返回队列的大小
  • empty():判断是否为空
  • full():判断是否满了
  • get():取出队列最后一个数据
  • put():见你那个一个数据放到队列中

可以避免线程争抢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章