Python并发编程

互斥锁

多个程序同时操作一份数据的时候很容易产生数据错乱
为了避免数据错乱,我们需要使用互斥锁

互斥锁作用:将并发变成串行(虽然牺牲了程序的执行效率但是保证了数据安全)

from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire()  # 抢锁
mutex.release()  # 释放锁

''' 互斥锁只应该出现在多个程序操作数据的地方,其他位置尽量不要加'''

线程理论

进程是资源单位

进程相当于是车间 进程负责给内部的线程提供相应的资源

线程是执行单位

线程相当于是车间里面的流水线 线程负责执行真正的功能

  • 一个进程至少含有一个线程

  • 多进程需要申请内存空间,需要拷贝全部代码,资源消耗大

  • 多线程不需要申请内存空间,也不需要拷贝全部代码,资源消耗小

  • 同一个进程下多个线程之间资源共享

创建线程的两种方式

开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写

封装函数

from threading import Thread
from multiprocessing import Process
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')


t = Thread(target=task, args=('jason',))
t.start()
print('主线程')
if __name__ == '__main__':
    t = Thread(target=task, args=('jason',))
    t.start()
    print('主线程')

class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(3)
        print(f'{self.name}运行结束')


obj = MyThread('jason')
obj.start()
print('主线程')

多线程实现TCP服务端并发

比多进程更加简单方便,消耗的资源更少

import socket
from threading import Thread

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

def servers(sock):
    while True:
        try:
            data = sock.recv(1024)
            if len(data) == 0: break
            data1 = data.decode('utf8')
            data2 = data1.upper()
            data3 = data2.encode('utf8')
            sock.send(data3)
        except ConnectionResetError as e:
            print(e)
            break
        print('服务端接收消息>>>:%s' % data1)


while True:
    sock, address = server.accept()
    p = Thread(target=servers, args=(sock,))
    p.start()

join方法

主线程等到子线程运行结束之后再运行
from threading import Thread
import time

def task():
    print('正在执行')
    time.sleep(3)
    print('运行结束')


t = Thread(target=task)
t.start()
t.join()
print('主线程')

同一个进程下线程间数据共享

from threading import Thread

money = 1000


def func():
    global money
    money = 666


t = Thread(target=func)
t.start()
t.join()  # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)

线程对象相关方法

  1. 进程号

    同一个进程下开设的多个线程拥有相同的进程号

    import os
    from threading import Thread, current_thread, active_count
    import time
    
    def task(name):
        print(f'{name}正在运行')
        time.sleep(5)
        # global money
        # money = 666
        print(f'{name}运行结束')
        print('子线程id:%s' % os.getpid())
        print('子线程名:%s' % current_thread().name)
    if __name__ == '__main__':
        print('主线程', os.getppid())
        for i in range(4):
            t = Thread(target=task, args=(f'xz{i}', ))
            t.start()
        print('活跃的线程数>>>:', active_count())
    '运行结果:
        主线程 3440
        xz0正在运行
        xz1正在运行
        xz2正在运行
        xz3正在运行
        活跃的线程数>>>: 5
        xz0运行结束xz1运行结束xz2运行结束
    
        子线程id:8564
        子线程名:Thread-2
        xz3运行结束子线程id:8564
    
        子线程名:Thread-3
        子线程id:8564
    
        子线程id:8564子线程名:Thread-1
    
        子线程名:Thread-4'
    
  2. 线程名

    from threading import Thread, current_thread
    	current_thread().name
     	主:MainThread	子:Thread-N
    
  3. 进程下的线程数

    active_count()
    

守护线程

守护线程伴随着被守护的线程的结束而结束

from threading import Thread
import time

def task():
    print('子线程运行task函数')
    time.sleep(2)
    print('子线程运行task结束')

t = Thread(target=task)
# t.daemon = True
t.start()
# t.daemon = True
print('主线程')

'''
1.开启守护线程的话,子线程伴随主线程结束而结束。
2.不开启守护线程的话,主线程要等全部子线程结束而结束。
3.在子线程中代码是异步执行操作,先是子代码运行task函数,再异步到主线程,然后子线程运行task结束。
'''

GIL全局解释器锁

  • GIL的研究是Cpython解释器的特点 不是python语言的特点

  • GIL本质也是一把互斥锁

  • GIL的存在使得同一个进程下的多个线程无法同时执行(关键)

    即单进程下的多线程无法利用多核优势,效率低

  • GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的

  1. 误解:python的多线程就是垃圾 利用不到多核优势

    python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的

  2. 误解:既然有GIL 那么以后我们写代码都不需要加互斥锁

    不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
    针对程序中自己的数据应该自己加锁处理

  3. 所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行

    ps:我们平时在写代码的时候 不需要考虑GIL 只在学习和面试阶段才考虑

posted @ 2022-08-11 02:26  梦想有双休  阅读(28)  评论(0)    收藏  举报