其他分享
首页 > 其他分享> > 互斥锁、线程理论、创建线程的多种方式、线程的相关方法、GIL全局解释器锁

互斥锁、线程理论、创建线程的多种方式、线程的相关方法、GIL全局解释器锁

作者:互联网

练习

尝试将TCP服务端制作成并发效果:客户端服务端全部设置成自动发消息自动回消息
eg: 客户端发hello 服务端直接转大写回HELLO

服务端:
import socket
from multiprocessing import Process
def server_get():
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    return server
def run(sock):
    while  True:
        try:
            data = sock.recv(1024)
            print(data.decode('utf8'))
            sock.send(data.upper())
        except ConnectionRefusedError as e:
            sock.close()
            break

if __name__ == '__main__':
    # 方式一
    # server = socket.socket()
    # server.bind(('127.0.0.1', 8080))
    # server.listen(5)
    server = server_get()
    while  True:
        sock,addr = server.accept()
        p = Process(target=run,args=(sock,))
        p.start()
        
客服端:
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    client.send('hello'.encode('utf8'))
    data = client.recv(1024)
    print(data.decode('utf8'))

互斥锁

多程序同时操作一份数据的时候,很容易产生数据的错乱,为了避免数据错乱,我们需要使用互斥锁;互斥锁是将并发变成串行,虽然牺牲了程序的执行效率但是保证了数据的安全

使用

互斥锁只应该在出现多个程序操作数据的地方,其他位置尽量不要加 。ps:以后我们自己处理的情况很少,只需要知道锁的功能即可

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

抢票系统

from multiprocessing import Process,Lock
import json
import time
import random


def select_ticket(name):
    with open(r'ticket.json','r',encoding='utf8')as f :
        data = json.load(f)
    print(name,f"余票:{data.get('ticket')}")


def buy_ticket(name):
    with open(r'ticket.json', 'r', encoding='utf8')as f:
        data = json.load(f)
    time.sleep(random.randint(1,3))

    if data.get('ticket') > 0:
        data['ticket'] -= 1
        print(name,"购买成功")
    else:
        print("没票了")
    with open(r'ticket.json', 'w', encoding='utf8')as f:
        json.dump(data,f)

def run(name,mutex):
    select_ticket(name)
    mutex.acquire()
    buy_ticket(name)
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    for i in range(10):
        p = Process(target=run,args=(i,mutex))
        p.start()

补充知识

1.行锁:访问数据库的时候,锁定整个行数据,他人不能在哪一行操作数据。
2.表锁:访问数据库的时候,锁定整个表数据,他人不能在哪一个表格操作数据。
3.乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁
4.悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁

线程理论

1.怎样理解线程和进程
	1.1 进程:进程是资源单位,进程相当于是车间,负责给内部的线程提供相应的资源
	1.2 线程: 线程是执行单位,线程相当于是车间里面的流水线,负责执行真正的功能
2.一个进程至少含有一个线程
3.多进程与多线程的区别
	3.1 多进程:需要申请内存空间,并且需要拷贝全部代码,资源消耗大
	3.2 多线程:不需要申请内存空间,也不需要拷贝全部代码,并且资源消耗小
4.同一个进程下多个线程之间资源共享

创建线程的两种方式

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

方式一

from threading import Thread
import time

def task(name):
    print(f'{name} 程序开始执行')
    time.sleep(3)
    print(f"{name} 程序执行结束")

if __name__ == '__main__':
    p = Thread(target=task,args=('nana',))
    p.start()
    print("主线程")
    
结果:
    nana 程序开始执行
    主线程
    nana 程序执行结束

方式二

from threading import Thread
import time

class My_Thread(Thread):

    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self):
        print(f'{self.name} 程序开始执行')
        time.sleep(3)
        print(f"{self.name} 程序执行结束")

if __name__ == '__main__':
    p = My_Thread('nana')
    p.start()
    print("主线程")

结果:
    nana 程序开始执行
    主线程
    nana 程序执行结束

多线程实现TCP服务端开发

服务端:
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

def run(sock):
    while  True:
        try:
            data = sock.recv(1024)
            print(data.decode('utf8'))
            sock.send(data.upper())
        except ConnectionRefusedError as e:
            sock.close()
            break
            
while  True:
    sock,addr = server.accept()
    p =  Thread(target=run,args=(sock,))
    p.start()
    
客服端:
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    client.send('hello'.encode('utf8'))
    data = client.recv(1024)
    print(data.decode('utf8'))

join()方法

join()方法作用:使主线程等到子线程运行结束之后再运行

from threading import Thread
import time

def task(name):
    print(f'{name} 程序开始执行')
    time.sleep(3)
    print(f"{name} 程序执行结束")

if __name__ == '__main__':
    p = Thread(target=task,args=('nana',))
    p.start()
    p.join()
    print("主线程")
    
结果:
    nana 程序开始执行
    nana 程序执行结束
    主线程

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

from threading import Thread
money = 1000
def task():
    global money
    money = 666
    print('子线程:',money)

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

结果:
    子线程: 666
    主线程: 666

线程对象相关方法

1.进程号

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

from threading import Thread
import os

def task(name):
    print("子线程号:",os.getpid())

if __name__ == '__main__':
    p1 = Thread(target=task,args=('nana',))
    p2 = Thread(target=task, args=('kevin',))
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    print("主线程号:",os.getpid())
    
结果:
    子线程号: 6680
    子线程号: 6680
    主线程号: 6680

2.线程名

导入from threading import current_thread,用current_thread().name查看线程名

from threading import Thread,current_thread
import time
import os

def task(name):
    print(f'{name} 程序开始执行')
    time.sleep(3)
    print(f"{name} 程序执行结束")
    print("子线程名:",current_thread().name)

if __name__ == '__main__':
    p1 = Thread(target=task,args=('nana',))
    p2 = Thread(target=task, args=('kevin',))
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    print("主线程名:",current_thread().name)
结果:
    nana 程序开始执行
    nana 程序执行结束
    子线程名: Thread-1
    kevin 程序开始执行
    kevin 程序执行结束
    子线程名: Thread-2
    主线程名: MainThread

3.查看进程下的线程数

导入from threading import active_count,用active_count()查看进程下的线程数

from threading import Thread,  active_count
import time

def task():
    time.sleep(3)

if __name__ == '__main__':
    for i in range(4):
        t = Thread(target=task, )
        t.start()
    print('当前进程下存活的线程数>>>:', active_count())

守护线程

守护线程随着主线程的结束而结束(随着被守护的线程的结束而结束)

用obj.daemon = True将子线程变为守护线程,obj.daemon = True写在obj.start()前面

注意:进程下所有的非守护线程结束,主线程(主进程)才能真正的结束

from threading import Thread
import  time

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

t = Thread(target=task)
t.daemon = True
t.start()
print('主线程')
子线程运行task函数
主线程

GIL全局解释器锁

储备知识

python解释器是由编程语言写出来的,Cpython:是用C语言写出来的,

Jpthon:是用java语言写出来的,Pypython:是用python语言写出来的

官方文档对GIL的解释

	In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
    
解读:
1.GIL的研究是Cpython解释器的特点,不是python语言的特点
2.GIL本质也是一把互斥锁
3.GIL的存在使得同一个进程下的多个线程不能同时执行(单进程下的多进程无法利用多核优势,使效率低)
4.GIL的存在主要是因为cpthon解释器中垃圾回收机制不是线程安全的

image

GIL的理解

1.判断:python的多线程就是垃圾,利用不到多核优势
	错误,python的多线程确实无法使用多核优势,但是在IO密集的任务下是有用的
2.判断:既然有GIL,那么以后我们写代码就不需要加互斥锁
	错误,GIL只确保解释器层面数据不会错乱(垃圾回收机制),针对程序中自己的数据应该自己加锁处理
3.所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行
ps:我们平时写代码的时候,不需要考虑GIL

标签:__,name,Thread,互斥,线程,print,import,GIL
来源: https://www.cnblogs.com/luonacx/p/16574143.html