记忆碎片之python多线程、线程安全、GIL锁、local()对象

创建多线程的方式

方式一:通过Thread创建多线程 函数式
方式二:通过继承Thread类创建多线程 class类

多线程和多进程之间区别
多进程是多份程序同时执行
多线程是在一份程序下多个执行指针同时执行
多线程并不需要线程间通信,线程间共享全局变量,进程间不共享全局变量
进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,
它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
且线程不能够独立执行,必须依存在进程中
线程执行开销小,但不利于资源的管理和保护,而进程正相反

from threading import Thread, ThreadError, Timer
import time


# 函数式
def wuzhe(dj):
    print("键盘上的舞者_" + dj)
    time.sleep(1)


# if __name__ == '__main__':
#     start_time = time.perf_counter()
#     for _ in range(5):
#         t = Thread(target=wuzhe, args=("DJ",))
#         t.start()
#     end_time = time.perf_counter()
#     print(f"程序耗时{(end_time - start_time).__round__(10)}")

#
# class类
class Wuzhe(Thread):
    def __init__(self, **kwargs):
        super(Wuzhe, self).__init__()
        self.goods = kwargs["goods"]

    def run(self):  # 名字必须定义为run,否则不会运行
        for i in range(3):
            time.sleep(1)
            print(f"当前是{self.name}在运行{str(i)},买的商品为{self.goods}")


def test():
    for j in range(5):
        t = Wuzhe(goods="馒头")
        t.start()


# if __name__ == '__main__':
#     test()
# 线程在执行过程中,如果中途执行sleep时,线程就会进入到阻塞的状态,当sleep结束之后,线程进入到就绪转台,等待
# 调度而线程调度将自行选择一个线程执行

# 线程之间共享全局变量
num = 100


def work1():
    global num
    for i in range(3):
        num += 1
    print(f"当前执行的是work1,num的值是{num}")


def work2():
    global num
    for i in range(3):
        num += 1
    print(f"当前执行的是work2,num的值是{num}")


# if __name__ == '__main__':
#     print("准备创建线程")
#     t1 = Thread(target=work1)
#     t1.start()
#     time.sleep(1)
#     t2 = Thread(target=work2)
#     t2.start()
#     print("over")
# 输出结果为:
# 准备创建线程
# 当前执行的是work1,num的值是103
# 当前执行的是work2,num的值是106over
# 说明:
# 在一个进程内的所有线程共享全局变量,能够在不适用其他方式的前提下完成多线程之间的数据共享(这点要比多进程要好)
# 缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)

# 线程不安全
nums = 0


def wuzhe1():
    global num
    for i in range(1000000):
        num += 1
    print(f"当前执行的是wuzhe1,num的值是{num}")


def wuzhe2():
    global num
    for i in range(1000000):
        num += 1
    print(f"当前执行的是wuzhe2,num的值是{num}")


# if __name__ == '__main__':
#     print("准备创建线程")
#     t1 = Thread(target=wuzhe1)
#     t1.start()
#     # time.sleep(3)
#     t2 = Thread(target=wuzhe2)
#     t2.start()
#     print("over", num)
# time.sleep(3)没有注释时
# 准备创建线程
# 当前执行的是wuzhe1,num的值是1000100
# over 1024274
# 当前执行的是wuzhe2,num的值是2000100
# time.sleep(3) 注释时
# 准备创建线程
# over 163934
# 当前执行的是wuzhe1,num的值是1178561
# 当前执行的是wuzhe2,num的值是1323692
# 没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期 ,这种现象就是线程不安全。

# 避免线程不安全
from threading import Lock

num_1 = 0


def wuzhe3():
    global num_1
    for i in range(1000000):
        # True表示堵塞 即如果这个锁在上锁之前已经被上锁了,那么这个线程会在这里一直等待到解锁为止
        # False表示非堵塞,即不管本次调用能够成功上锁,都不会卡在这,而是继续执行下面的代码
        mutexFlag = mutex.acquire(True)
        if mutexFlag:
            num_1 += 1
            mutex.release()
    print(f"当前执行的是wuzhe3,num的值是{num_1}")


def wuzhe4():
    global num_1
    for i in range(1000000):
        mutexFlag = mutex.acquire(True)
        if mutexFlag:
            num_1 += 1
            mutex.release()
    print(f"当前执行的是wuzhe4,num的值是{num_1}")


# if __name__ == '__main__':
#     print("准备创建线程")
#     # 创建一个互斥锁对象
#     # 加锁确保了某段关键代码只能由一个线程从头到尾完整地执行,
#     # 但是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,
#     # 效率就大大地下降了,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
#     mutex = Lock()
#     t1 = Thread(target=wuzhe3)
#     t1.start()
#     # time.sleep(3)
#     t2 = Thread(target=wuzhe4)
#     t2.start()
#     print("over", num_1)
# time.sleep(3)没有注释
# 准备创建线程
# 当前执行的是wuzhe3,num的值是1000000
# over 1005606
# 当前执行的是wuzhe4,num的值是2000000
# time.sleep(3)注释
# 准备创建线程
# over 31644
# 当前执行的是wuzhe3,num的值是1909435
# 当前执行的是wuzhe4,num的值是2000000

# 死锁
# 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同事等待对方的资源,就会造成死锁
class Wuzhe1(Thread):
    def run(self) -> None:
        if mutexA.acquire():
            print(self.name + "----do1---up----")
            time.sleep(1)
            if mutexB.acquire():
                print(self.name + "----do1---down----")
                mutexB.release()
            mutexA.release()


class Wuzhe2(Thread):
    def run(self) -> None:
        if mutexB.acquire():
            print(self.name + "----do1---up----")
            time.sleep(1)
            if mutexA.acquire():
                print(self.name + "----do1---down----")
                mutexA.release()
            mutexB.release()


mutexA = Lock()
mutexB = Lock()
# if __name__ == '__main__':
#     t1 = Wuzhe1()
#     t2 = Wuzhe2()
#     t1.start()
#     t2.start()

# 可以通过生产者与消费者模型来解决线程的同步和线程的安全
# 使用队列,队列实现来锁原理(原子性操作,要么不搞,要么就搞完),通过使用队列来实现线程间的同步
from queue import Queue


# 生产者
class Producer(Thread):
    def run(self) -> None:
        global queue
        count = 0
        while True:
            if queue.qsize() < 1000:
                for i in range(100):
                    count += 1
                    info = "生产产品" + str(count)
                    queue.put(info)
                    print(info)
            time.sleep(0.5)


# 消费者
class Consumer(Thread):
    def run(self) -> None:
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    info = self.name + "消费了" + queue.get()
                    print(info)
            time.sleep(1)


# if __name__ == '__main__':
#     queue = Queue()
#     # 这个阻塞队列就是用来给生产者和消费者解耦的
#     for i in range(500):
#         queue.put("初始产品" + str(i))
#     for p1 in range(2):
#         p = Producer()
#         p.start()
#     for c1 in range(5):
#         c = Consumer()
#         c.start()

# 使用全局变量但是不添加锁
from threading import local, current_thread


def process_student():
    # 获取当前线程关联的student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, current_thread().name))


def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()


local_school = local()
# if __name__ == '__main__':
#     t1 = Thread(target=process_thread, args=('键盘',), name='Thread-A')
#     t2 = Thread(target=process_thread, args=('舞者',), name='Thread-B')
#     t1.start()
#     t2.start()
#     t1.join()
#     t2.join()
# 输出:
# Hello, 键盘 (in Thread-A)
# Hello, 舞者 (in Thread-B)
# 全局变量local_school就是一个local()对象,每个Thread对它都可以读写student属性,但互不影响。
# 你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,
# 可以任意读写而互不干扰,也不用管理锁的问题,local()内部会处理。
# 可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。
# local()最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,
# 这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
# 一个local()变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。
# local()解决了参数在一个线程中各个函数之间互相传递的问题

# 同步调用和异步调用
# 同步调用就是你喊你朋友吃饭,你朋友在忙,你就一直在那等,等你朋友忙完了 ,你们一起去。
# 异步调用就是你喊你朋友吃饭,你朋友说知道了,待会忙完去找你 ,你就去做别的了。

from multiprocessing import Pool
import os


def wuzhe3():
    print(f"进程池中的进程{os.getpid()},父id为{os.getppid()}")
    for i in range(3):
        print(f"------{i}-----")
        time.sleep(1)
    return "hello"


def wuzhe4(args):
    print(f"------------回调函数-----pid===={os.getpid()}")
    print(f"------------回调函数-----参数===={args}")


# if __name__ == '__main__':
#     pool = Pool()
#     pool.apply_async(func=wuzhe3, callback=wuzhe4)
#     time.sleep(5)
#     print(f"主进程的pid为---{os.getpid()}")
    # 这里的callback是由主进程执行的,当子进程死亡,主进程回调函数。

# GIL锁
# Python全局解释锁(GIL)简单来说就是一个互斥体(或者说锁),这样的机制只允许一个线程来控制Python解释器。
# 这就意味着在任何一个时间点只有一个线程处于执行状态。
# 所以在python中多线程是假的,因为在执行过程中CPU中只有一个线程在执行。
# 当你使用多进程时,你的效率是高于多线程的。
# Python GIL经常被认为是一个神秘而困难的话题,但是请记住作为一名Python支持者,
# 只有当您正在编写C扩展或者您的程序中有计算密集型的多线程任务时才会被GIL影响。

发布了46 篇原创文章 · 获赞 7 · 访问量 4621

猜你喜欢

转载自blog.csdn.net/Python_DJ/article/details/105146574