线程 *知识点扩充

1.  基于 TCP 协议实现 socket 通信 多线程并发 效果(简易版本)


# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8083))

while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data.decode('utf-8'))

# 服务端
TCP服务端实现并发
  1.将不同的功能尽量拆分成不同的函数
    拆分出来的功能可以被多个地方使用
  
  1.将连接循环和通信循环拆分成不同的函数
  2.将通信循环做成多线程
import socket from threading import Thread def run(conn, i): while True: try: data = conn.recv(1024) print('第%s条数据:' % i, data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() server = socket.socket() server.bind(('127.0.0.1', 8083)) server.listen(5) while True: conn, addr = server.accept() for i in range(10): t = Thread(target=run, args=(conn, i)) t.start()

 2. 全局解释器锁 GIL(global interpreter lock)

  GIL本质也是一把互斥锁:将并发变成串行牺牲效率,保证数据的安全

  作用:阻止同一个进程下的多个线程的同时执行

      也就意味着 同一个进程内多个线程无法实现并行但是可以实现并发

      python的多线程没法利用多核优势  是不是就是没有用了?   

          研究python的多线程是否有用需要分情况讨论:(肯定是有用的)

             假设有四个任务(每个任务都需要10s)

              都是计算密集型:

                单核情况下
                      开线程更省资源
                多核情况下
                      开进程 10+s
                      开线程 40+s

              都是 IO 密集型 :
                单核情况下
                      开线程更节省资源
                多核情况下

                   开线程更节省资源

 

  PS:python 解释器有很多种,最常见的就是 CPython 解释器   在CPython解释器才有GIL的概念,不是python的特点

 

     GIL 的存在是因为 CPython 解释器的 内存管理的线程不是安全 的      

       内存管理机制:
          引用计数值与变量的绑定关系的个数
          标记清除:当内存快要满的时候 会自动停止程序的运行 检测所有的变量与值的绑定关系
                  给没有绑定关系的值打上标记,最后一次性清除
          分代回收:(垃圾回收机制也是需要消耗资源的,而正常一个程序的运行内部会使用到很多变量与值
                    并且有一部分类似于常量,减少垃圾回收消耗的时间,应该对变量与值的绑定关系做一个分类)

               新生代(5S)    >>>   青春代(10s)    >>>   老年代(20s)
                 垃圾回收机制扫描一定次数发现关系还在,会将该对关系移至下一代
                 随着代数的递增 扫描频率是降低的

        如:多个线程同时处理同一进程下的一份数据,假如没有全局解释器锁,容易造成数据的错乱

 

# 计算密集型
# from multiprocessing import Process
# from threading import Thread
# import os,time
# def work():
#     res=0
#     for i in range(100000000):
#         res*=i
#
#
# if __name__ == '__main__':
#     l=[]
#     print(os.cpu_count())  # 本机为6核
#     start=time.time()
#     for i in range(6):
#         # p=Process(target=work) #耗时  4.732933044433594
#         p=Thread(target=work) #耗时 22.83087730407715
#         l.append(p)
#         p.start()
#     for p in l:
#         p.join()
#     stop=time.time()
#     print('run time is %s' %(stop-start))

 

# IO密集型
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为6核
    start=time.time()
    for i in range(4000):
        p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) #耗时2.051966667175293s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

 

3. 全局解释器 GIL & 普通互斥锁

我们从 全局解释器锁  未加锁但是让程序出现IO操作情况  以及  加锁 情况来比较
# 全局解释器锁

def run(i):
    global n
    re_n = n
    n = re_n - 1
    print(f'第{i}个线程修改了n,现在 n = {n}')


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=run, args=(i,))
        t.start()


# 由于有全局解释器锁的存在,多线程操作数据正常

'''
# 第0个线程修改了n,现在n = 99
        # 第1个线程修改了n,现在n = 98
        # 第2个线程修改了n,现在n = 97
        # 第3个线程修改了n,现在n = 96
        # 第4个线程修改了n,现在n = 95
        # 第5个线程修改了n,现在n = 94
        # 第6个线程修改了n,现在n = 93
        # 第7个线程修改了n,现在n = 92
        # 第8个线程修改了n,现在n = 91
        # 第9个线程修改了n,现在n = 90
        # 
        # Process finished with exit code 0
'''

 

# 不加锁但是 让程序出现IO操作

from threading import Thread
import time

n = 100


def run(i):
    global n
    re_n = n
    time.sleep(1)  # IO操作时候回自动释放GIL
    n = re_n - 1
    print(f'第{i}个线程修改了n,现在 n = {n}')


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=run, args=(i,))
        t.start()

# 由于多线程程序遇到了IO操作,使得数据错乱
'''
    # 第1个线程修改了n,现在 n = 99
        # 第0个线程修改了n,现在 n = 99
        # 第6个线程修改了n,现在 n = 99
        # 第4个线程修改了n,现在 n = 99
        # 第2个线程修改了n,现在 n = 99
        # 第3个线程修改了n,现在 n = 99
        # 第7个线程修改了n,现在 n = 99
        # 第5个线程修改了n,现在 n = 99
        # 第8个线程修改了n,现在 n = 99
        # 第9个线程修改了n,现在 n = 99
        # 
        # Process finished with exit code 0
'''

 

# 加锁
from threading import Thread, Lock
import time


mutex = Lock()  # 生成一把锁

n = 100


def run(i):
    mutex.acquire()  # 抢锁
    global n  # 局部修改全局
    re_n = n
    time.sleep(1)  # IO操作时候回自动释放GIL,但是由于自己加了一把锁,所以还是在修改数据时候并发变成串行
    n = re_n - 1
    print(f'第{i}个线程修改了n,现在 n = {n}')
    mutex.release()  # 释放锁


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=run, args=(i,))
        t.start()

# 加锁情况下,数据得到了保障,结果正常  此时同时有 GIL 和 锁
'''
第0个线程修改了n,现在 n = 99
第1个线程修改了n,现在 n = 98
第2个线程修改了n,现在 n = 97
第3个线程修改了n,现在 n = 96
第4个线程修改了n,现在 n = 95
第5个线程修改了n,现在 n = 94
第6个线程修改了n,现在 n = 93
第7个线程修改了n,现在 n = 92
第8个线程修改了n,现在 n = 91
第9个线程修改了n,现在 n = 90

Process finished with exit code 0
'''

 

 4. 死锁现象(了解知识点)

from threading import Thread, Lock
import time

mutex1 = Lock()
mutex2 = Lock()

def run(i):
    run1(i)
    run2(i)


def run1(i):
    mutex1.acquire()
    print(f'''
    
第 {i} 个线程 第一次 抢到了 mutex1 锁''')
    mutex2.acquire()
    print(f'第 {i} 个线程 第一次 抢到了 mutex2 锁')
    mutex2.release()
    mutex1.release()


def run2(i):
    mutex2.acquire()
    print(f'''
    
第 {i} 个线程 第二次 抢到了 mutex2 锁''')
    time.sleep(1)
    mutex1.acquire()
    print(f'第 {i} 个线程 第二次 抢到了 mutex1 锁')
    mutex1.release()
    mutex2.release()


if __name__ == '__main__':
    for i in range(5):
        t = Thread(target=run, args=(i,))
        t.start()

# 结果
'''
第 0 个线程 第一次 抢到了 mutex1 锁
第 0 个线程 第一次 抢到了 mutex2 锁

    
第 0 个线程 第二次 抢到了 mutex2 锁

    
第 1 个线程 第一次 抢到了 mutex1 锁
'''
# 上述结果可以看到,由于第0个线程在第二次抢锁时候,刚抢完锁1,准备抢锁2时候,发现锁2已经被线程1给抢走了,只能继续停在原地,代码执行不动,也就是死锁

# RLock(递归锁)
"""
Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢

"""
from threading import Thread, RLock
import time


mutex1 = mutex2 = RLock()  # Rlock可以被第一个抢到锁的人连续的acquire和release
# 此时 mutex1 和 mutex2 是同一把锁

def run(i):
    run1(i)
    run2(i)


def run1(i):
    mutex1.acquire()
    print(f'''
    
第 {i} 个线程 第一次 抢到了 mutex1 锁''')
    mutex2.acquire()
    print(f'第 {i} 个线程 第一次 抢到了 mutex2 锁')
    mutex2.release()
    mutex1.release()


def run2(i):
    mutex2.acquire()
    print(f'''
    
第 {i} 个线程 第二次 抢到了 mutex2 锁''')
    time.sleep(1)
    mutex1.acquire()
    print(f'第 {i} 个线程 第二次 抢到了 mutex1 锁')
    mutex1.release()
    mutex2.release()


if __name__ == '__main__':
    for i in range(5):
        t = Thread(target=run, args=(i,))
        t.start()

# 结果
第 0 个线程 第一次 抢到了 mutex1 锁
第 0 个线程 第一次 抢到了 mutex2 锁

    
第 0 个线程 第二次 抢到了 mutex2 锁
第 0 个线程 第二次 抢到了 mutex1 锁

    
第 1 个线程 第一次 抢到了 mutex1 锁
第 1 个线程 第一次 抢到了 mutex2 锁

    
第 1 个线程 第二次 抢到了 mutex2 锁
第 1 个线程 第二次 抢到了 mutex1 锁

    
第 3 个线程 第一次 抢到了 mutex1 锁
第 3 个线程 第一次 抢到了 mutex2 锁

    
第 3 个线程 第二次 抢到了 mutex2 锁
第 3 个线程 第二次 抢到了 mutex1 锁

    
第 2 个线程 第一次 抢到了 mutex1 锁
第 2 个线程 第一次 抢到了 mutex2 锁

    
第 2 个线程 第二次 抢到了 mutex2 锁
第 2 个线程 第二次 抢到了 mutex1 锁

    
第 4 个线程 第一次 抢到了 mutex1 锁
第 4 个线程 第一次 抢到了 mutex2 锁

    
第 4 个线程 第二次 抢到了 mutex2 锁
第 4 个线程 第二次 抢到了 mutex1 锁

Process finished with exit code 0

 5. 信号量(了解知识点)

from threading import Semaphore, Thread
import time, random

sm = Semaphore(5)  # 5表示容量个数


def run(i):
    sm.acquire()  # 能同时接纳5个
    print(f'{i}占了一个位置')
    time.sleep(random.randint(1, 3))
    sm.release()  # 释放一个空出来一个,可以继续接纳
    print(f'{i}释放了一个位置')


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=run, args=(i,))
        t.start()

# 结果
0占了一个位置
1占了一个位置
2占了一个位置
3占了一个位置
4占了一个位置
3释放了一个位置
    
5占了一个位置
1释放了一个位置
    
6占了一个位置
0释放了一个位置
7占了一个位置
2释放了一个位置
    
8占了一个位置

4释放了一个位置
    
6释放了一个位置
    
9占了一个位置
5释放了一个位置
    
9释放了一个位置
    
8释放了一个位置
    
7释放了一个位置
    

Process finished with exit code 0

 6. event 事件(了解知识点)

from threading import Event, Thread
import time

e = Event()  # 生成一个Event对象


def run():
    print('      红灯....')
    time.sleep(3)
    e.set()  # 发信号
    print('      绿灯... ')


t = Thread(target=run)
t.start()


def run1(i):
    print(f'公交 {i + 1} 路停车等待....')
    e.wait()  # 等待信号
    print(f'公交 {i + 1} 路开始百公里加速....')


for i in range(10):
    t = Thread(target=run1, args=(i,))
    t.start()

# 结果
'''
      红灯....
公交 1 路停车等待....
公交 2 路停车等待....
公交 3 路停车等待....
公交 4 路停车等待....
公交 5 路停车等待....
公交 6 路停车等待....
公交 7 路停车等待....
公交 8 路停车等待....
公交 9 路停车等待....
公交 10 路停车等待....
      绿灯... 
公交 1 路开始百公里加速....
公交 4 路开始百公里加速....
公交 5 路开始百公里加速....
公交 8 路开始百公里加速....
公交 9 路开始百公里加速....
公交 2 路开始百公里加速....
公交 10 路开始百公里加速....
公交 6 路开始百公里加速....
公交 3 路开始百公里加速....
公交 7 路开始百公里加速....

Process finished with exit code 0
'''

7. 线程 q(了解知识点)

from queue import Queue, LifoQueue, PriorityQueue


q1 = Queue()  # 先进先出队列
q1.put(2)  # 先放
q1.put(1)
q1.put(3)

print(q1.get())  # 2

q2 = LifoQueue()  # 后进先出队列 last in first out
q2.put(2)
q2.put(3)
q2.put(5)  # 后放

print(q2.get())  # 5

q3 = PriorityQueue()  # 优先级队列,一个元祖参数,元祖内第一个参数表示优先级,越小越优先,支持负数;元组内第二个参数为要放入的数据
q3.put((10, 4))
q3.put((20, 5))
q3.put((-10, 6))  # 优先级最高
q3.put((0, 7))

print(q3.get())  # (-10, 6)
print(q3.get()[1])  # 6 元祖是有序的,支持索引取值

 

posted @ 2019-08-14 20:16  速8赛亚人  阅读(206)  评论(0编辑  收藏  举报