昨日内容回顾
	IPC机制
		进程间通信
			进程与进程之间是数据隔离的
			管道/队列(管道+锁)
				队列:先进先出
				堆栈:先进后出
				q.put()  放入值
				q.get()  获取队列里面的值(同一时刻只能有一个任务来队列中获取数据)
				两者在存放值和取值的时候都会出现阻塞的情况(队列满了,队列空了)
		
		
		生产者消费者模型
			生产者:生产数据(做包子的)
			消费者:处理数据(吃包子的)
			两者之间的通信介质:队列/管道
			供需不平衡:
				队列:	生产者生产的数据放到队列里面
						消费者去队列里面获取数据
		
		线程理论
		
			把操作系统比喻成工厂
			进程:资源单位(工厂里面的车间)
			线程:执行单位(车间里面的流水线)
			任何一个进程都自带一个"主"线程
			
			进程中的线程数据是共享的,
			开起进程的开销要远远大于开启线程的开销
			开启进程:申请空间,拷贝代码都需要耗时
			开启线程:开销极小,几乎在代码执行的同时线程就已经创建
		
				
		如何起线程?(跟起进程一样一样的)
			两种起线程的方式:
				1.通过制定target起
				2.通过继承类的方式起
		
		线程join
			主线程等待子线程运行完毕
		
		线程之间数据共享
			多个线程操作同一份数据???出现数据不安全的情况
			设计到多个线程或进程操作同一份数据的时候,通常都需要将并行并发变成串行
			虽然牺牲了效率但是提高了数据的安全性
			针对不同的数据,需要加不同的锁
			锁(独立卫生间)
		
		其他属性和方法
			current_thread().name
			active_count  当前活跃的线程数
			
    
		守护线程
			主线程必须等待所有非守护线程的结束才能结束

1.全局解释器锁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.)
'''
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
  • GIL其实就是一把互斥锁(牺牲了效率但是保证了数据的安全)。

  • 线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行

  • 同一时刻同一个进程内多个线程无法实现并行,但是可以实现并发

  • 为什么要有GIL是因为它内部的垃圾回收机制不是线程安全的

  • 垃圾回收机制也是一个任务,跟你的代码不是串行运行,如果是串行会明显有卡顿

  • 这个垃圾回收到底是开进程还是开线程?肯定是线程,线程肯定也是一段代码,所以想运行也必须要拿到python解释器

  • 假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!

  • 也就意味着在Cpython解释器上有一把GIL全局解释器锁

  • 同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行

1.python中的多线程到底有没有用?

单核情况下:四个任务

多核情况下:四个任务

计算密集型:一个任务算十秒,四个进程和四个线程,肯定是进程快

IO密集型:任务都是纯io情况下,线程开销比进程小,肯定是线程好


垃圾回收机制
    1.引用计数
    2.标记清除
    3.分代回收
    
同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行

问题:python多线程是不是就没有用了呢?
四个任务:计算密集的任务   每个任务耗时10s
单核情况下:
    多线程好一点,消耗的资源少一点
多核情况下:
    开四个进程:10s多一点
    开四个线程:40s多一点

四个任务:IO密集的任务   每个任务io 10s
单核情况下:
    多线程好一点
多核情况下:
    多线程好一点 
多线程和多进程都有自己的优点,要根据项目需求合理选择

# 计算密集型
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())  # 本机为12核
    start=time.time()
    for i in range(12):
        # p=Process(target=work) #耗时8s多
        p=Thread(target=work) #耗时44s多
        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()) #本机为12核
    start=time.time()
    for i in range(400):
        p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

#### 2.GIL与自定义互斥锁

不同的数据需要加不同的锁才能保证数据的安全,GIL锁只是对线程加锁,对数据并没有加锁的效果

​```python
from threading import Thread,Lock
import time

mutex=Lock()
n=100
def task():
    global n
    with mutex:
        temp=n
        time.sleep(0.1)
        n=temp-1

if __name__ == '__main__':
    l=[]
    for i in range(100):
        t=Thread(target=task)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print(n)
# 对于修改不同的数据,需要加不同的锁进行处理

3.死锁与递归锁(了解)

自定义锁一次acquire必须对应一次release,不能连续acquire

递归锁可以连续的acquire,每acquire一次计数加一

递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到我的人
from threading import Thread,Lock,RLock
import time

# mutexA=Lock()
# mutexB=Lock()
mutexB=mutexA=RLock()  # 抢锁之后会有一个计数 抢一次计数加一 针对的是第一个抢到我的人


class Mythead(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print('%s 抢到A锁' %self.name)
        mutexB.acquire()
        print('%s 抢到B锁' %self.name)
        mutexB.release()
        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print('%s 抢到了B锁' %self.name)
        time.sleep(2)
        mutexA.acquire()
        print('%s 抢到了A锁' %self.name)
        mutexA.release()
        mutexB.release()

if __name__ == '__main__':
    for i in range(100):
        t=Mythead()
        t.start()

4.信号量(了解)

自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致

from threading import Thread,Semaphore
import time
import random
sm = Semaphore(5)  # 公共厕所里面有五个坑位,在厕所外面放了五把钥匙

def task(name):
    sm.acquire()
    print('%s正在蹲坑'%name)
    # 模拟蹲坑耗时
    time.sleep(random.randint(1,5))
    sm.release()


if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task,args=('伞兵%s号'%i,))
        t.start()

5.Event事件

一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样

from threading import Thread,Event
import time
event = Event()  # 造了一个红绿灯


def light():
    print('红灯亮着的')
    time.sleep(3)
    print('绿灯亮了')
    event.set()


def car(name):
    print('%s 车正在等红灯'%name)
    event.wait()
    print('%s 车加油门飙车走了'%name)


if __name__ == '__main__':
    t = Thread(target=light)
    t.start()

    for i in range(10):
        t = Thread(target=car,args=('%s'%i,))
        t.start()

6.线程queue

同一个进程下的线程数据都是共享的为什么还要用queue?queue本身自带锁的功能,能够保证数据的安全

# 我们现在的q只能在本地使用,后面我们会学基于网络的q 
import queue

queue.Queue() #先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

queue.LifoQueue() #后进先出->堆栈
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

queue.PriorityQueue() #优先级
q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get())
原文地址:https://www.cnblogs.com/huangxuanya/p/10851150.html