python语法基础-并发编程-线程-长期维护

meylovezn 2020-02-10

###############   线程和GIL,全局解释器锁    ##############

"""
线程

为什么会有进程?
主要是能够同时处理多个任务,多个任务还要进行切换,时间片轮转
为什么会有线程?
进程并不是执行任务的最小单元,每一个进程里面有都一个线程,我们叫做主线程,
早期没有线程,一个进程只能干一个任务,如果有多个任务,只能多起进程,进程太多不行的,
进程内部的内存是共享的,所以需要线程,否则还要涉及到进程间的通信,这比较浪费资源

所以线程的出现解决了两个问题:
1,进程内部的通信
2,一个进程可以处理多个任务,

线程的开销比进程少,可以认为是一个轻型的进程,
进程可以任务是车间,线程可以认为是每一个工人,

进程是资源分配的最小单位,线程是cpu调度的最小单位,



###########################
进程和线程的区别:
1,进程之间的内存是独立的,但是一个进程之内的线程是可以共享的,
2,进程之间切换是慢于线程之间的切换的,

"""

# 第一个例子
from threading import Thread
import time,os
# 多线程并发
# def func(n):  # 这是子线程完成的
#     time.sleep(1)  # 虽然是打印了10次,但是只等待了1秒,所以10线程之间是并发的,
#     print(n)
#
# for i in range(10):  # 启动10个线程,
#     t = Thread(target=func,args=(1,))  # 注册
#     t.start()  # 这是启动了一个线程

# 第二种启动线程的方法:
# class MyTread(Thread):
#
#     def __init__(self,arg):
#         super().__init__()
#         self.arg=arg
#     def run(self):
#         time.sleep(1)
#         print(1,os.getpid())
#
#
# for i in range(10):
#     t = MyTread(10)  # 传递参数
#     t.start()
#
# print("主线程",os.getpid())  # 打印子进程和主进程的进程号,都是一样的

# 进程里面存了导入的模块,文件所在的位置,内置的函数代码
# 线程里面存了少量的必不可少代码,比如做加法运算,需要的参数,


# 所以目前看起来进程和线程的启动是非常的类似的,
#########################################
# 同一个数据,多个线程去操作,也会出现问题,所以也有线程锁的概念,
# 这种机制叫做全局解释器锁,英文GIL,
# 这种机制的结果就是:同一时间只有一个线程去访问cpu,
# 你想要访问数据,就必须拿到这个钥匙,
# 这个锁,所的是线程,不是锁的某一个数据,
# 这样不好,因为同一时间cpu上面只有一个线程,这样不能充分的利用cpu
# 这不是Python语言的问题,这是cPython解释器的问题,如果你有一个jPython解释器就行

# 这的确是一个弊病,导致Python的多线程形同虚设,
# 那么为什么不解决呢?
# java和c++都是编译性语言,Python是一个解释性语言,php也是,
# 目前解释性语言就是存在这个问题,这个矛盾还不可调和,

# 是否把数据加锁就可以了,也是不行的,数据太多了,这么大范围的加锁,最终导致的效率,还不如全局解释器锁的性能好,

# 在cPython解释器下的Python程序,在同一时间,多个线程只能有一个线程在cpu执行,
# 这不意味着多线程就没有意义了,
# 因为只有涉及到计算的地方才会使用到CPU,
# 高CPU:所以在计算类的高cpu利用率的,Python不占优势,
# 高IO:我们写的代码很多都涉及到这种,
# 比如qq聊天,处理日志文件,爬取网页,处理web请求,读写数据库,都是高io的,都是有Python用武之地的,
# 所以Python不能处理计算类高的问题,这不影响他在web编程的作用,


# 如果你真的需要高并发呢,你可以使用多进程,就不会有GIL锁了,



#######################################
# Threading中的其他方法
import threading
def func(n):
    print(n,threading.current_thread())  # <Thread(Thread-1, started 5428)>

for i in range(10):
    threading.Thread(target=func,args=(1,)).start()
    
print(threading.active_count())  # 查看活跃的线程数,
print(threading.current_thread())

###############    守护线程    ##############

# 守护线程:

from threading import Thread
import time
def func1(name):
    while True:
        print(11111111)
        time.sleep(1)

def func2(name):
    print(2222222)
    time.sleep(5)


if __name__ == ‘__main__‘:
    t=Thread(target=func1,args=(‘andy‘,))
    t.daemon = True  # 主线程代码结束,子线程随之结束,
    # 不加守护线程,主线程就会等待子线程的结束,然后主线程才会结束,
    t.start()

    t2=Thread(target=func2,args=(‘lucy‘,))
    t2.start()
    # 主线程会等待子线程结束
    print(‘主线程‘)
    print(t.is_alive())
    ‘‘‘
    主线程
    True
    ‘‘‘


# 所以进程的守护进程,和线程的守护进程是有不一样的点的
# 1.对主进程来说,运行完毕指的是主进程代码运行完毕
# 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
# 这是一个盲点,后续需要再看看,

 ###############    线程锁---互斥锁,死锁,递归锁,    ##############

# 线程锁
# 你用进程锁的时候不多,线程锁用的多
# 为什么会用到线程锁?
# 就是因为多个线程同时操作一个资源会出问题,

# 一般的锁,lock,叫做互斥锁,就是说你只能拿到你把钥匙,
# 死锁
# 递归锁,你拿到一把钥匙,就拿到了这个钥匙串,别的线程就拿到不到这个钥匙串了,


from threading import Lock,Thread
import time

# 模拟多线程出错的场景,
# 全局的n是10,然后创建10个线程,去执行n-1的操作,
# 结果按理说应该是0,但是结果是9,
# 为什么?
# 10个线程去拿n=10,所有人拿的都是10,然后都同步减去1,然后返回都是9,所以是9,
# 但是有GIL锁啊,为什么还会产生这种情况?
# 还是因为时间片轮转,你取到了数据,但是还没有来得及减1,这个值就被其他的线程拿走了,还是10,

#
# def func():
#     global n
#     temp = n
#     time.sleep(0.2)
#     n= temp-1
#
# n = 10
# t_list=[]
# for i in range(10):
#     t= Thread(target=func)
#     t.start()
#     t_list.append(t)
#
#
# for i in t_list:t.join()
# print(n)



# 模拟线程加锁的情况,然后看看结果
# 这样加了锁之后,结果应该就是0了,是的,结果是0了
# 加锁降低了性能,但是保证了数据的安全性,

# def func(lock):
#     global n
#     lock.acquire()
#     temp = n
#     time.sleep(0.2)
#     n= temp-1
#     lock.release()
#
# n = 10
# t_list=[]
# lock = Lock()
#
# for i in range(10):
#     t= Thread(target=func,args=(lock,))
#     t.start()
#     t_list.append(t)
#
#
# for i in t_list:t.join()
# print(n)


# 现在看看科学家吃面的问题,看看死锁的场景
# 这个例子是只有拿到面和叉子,才可以吃面,
#
#
# noodle_lock = Lock()
# fork_lock = Lock()
# def eat1(name):
#     noodle_lock.acquire()
#     print("%s拿到面条"%name)
#     fork_lock.acquire()
#     print("%s拿到叉子"%name)
#     print("吃面")
#     fork_lock.release()
#     noodle_lock.release()
#
#
#
# def eat2(name):
#     fork_lock.acquire()
#     print("%s拿到叉子"%name)
#     time.sleep(1)
#     noodle_lock.acquire()
#     print("%s拿到面条"%name)
#     print("吃面")
#     noodle_lock.release()
#     fork_lock.release()
#
# Thread(target=eat1,args=("name1",)).start()
# Thread(target=eat2,args=("name2",)).start()
# Thread(target=eat1,args=("name3",)).start()
# Thread(target=eat1,args=("name4",)).start()
#
# """
# name1拿到面条
# name1拿到叉子
# 吃面
# name2拿到叉子
# name3拿到面条
# 这种情况不同的人拿到了叉子和面条,就无法进行了,就阻塞了,就死锁了,
# 怎么解决?有一个递归锁
# """


# 递归锁,来解决死锁的问题
# # 你可以看做是钥匙串上面的两把钥匙,一旦你拿到了一把钥匙,就证明你拿到了整个钥匙串了,
from threading import RLock

noodle_lock = fork_lock = RLock()  # 你可以看做是钥匙串上面的两把钥匙
def eat1(name):
    noodle_lock.acquire()
    print("%s拿到面条"%name)
    fork_lock.acquire()
    print("%s拿到叉子"%name)
    print("吃面")
    fork_lock.release()
    noodle_lock.release()



def eat2(name):
    fork_lock.acquire()
    print("%s拿到叉子"%name)
    time.sleep(1)
    noodle_lock.acquire()
    print("%s拿到面条"%name)
    print("吃面")
    noodle_lock.release()
    fork_lock.release()

Thread(target=eat1,args=("name1",)).start()
Thread(target=eat2,args=("name2",)).start()
Thread(target=eat1,args=("name3",)).start()
Thread(target=eat2,args=("name4",)).start()

###############   信号量和事件   ##############

# 信号量
# 信号量就是控制只能有n个线程能访问这段代码

# from threading import Semaphore,Thread
# import time
# def func(sem,a,b):
#     sem.acquire()
#     time.sleep(1)
#     print(a+b)
#     sem.release()
#
# sem = Semaphore(4)
# for i in range(10):
#     t = Thread(target=func,args=(sem,i,i+5))
#     t.start()


# 事件:
# 事件被创建的时候是false状态,这个false状态会导致wait被阻塞,
# true状态的时候,wait就不阻塞了
# clear,设置为false
# set,设置状态未false,
# 上次举例的是红绿灯的例子,


# 现在我们举一个例子,检测数据库的可连接情况
# 启动两个线程,
# 第一个线程连接数据库,
# 等待一个信号,告诉我们之间的网络是通的
# 第二个线程,检测和数据自己之间的网络是否是通的
# 通了之后把事件的状态改为true,


import time,random
from threading import Thread,Event
def connect_db(e):
    count = 0
    while count < 3:
        # e.wait()  # 这个是一直等待,这种比较浪费资源,
        e.wait(0.1)  # 这是我只等待1秒,否则我就不等你了,# 但是我应该有一个重连的过程,比如三次,三次都连接不上,就断掉了
        if e.is_set() == True:
            print("连接数据库")
            break
        else:
            print("连接失败")
            count += 1
    else:
        raise TimeoutError("数据库连接超时")  # 主动报异常


def check_web(e):
    time.sleep(random.randint(0,2))
    e.set()

e = Event()
t1 = Thread(target=connect_db,args=(e,))
t2 = Thread(target=check_web,args=(e,))
t1.start()
t2.start()

###############  线程的条件和定时器    ##############

# 条件
from threading import Condition,Thread,Timer

# 可以把条件视为一个更加复杂的锁
# 同时也有两个方法,acquire,release
# 一个条件被创建,默认也是一个false状态,会影响wait处于等待状态
# notify(int),这是制造钥匙,参数是制造多少把钥匙
# 这个几乎就是只会出现在面试里面,平时基本不会用到,
#
# def func(con,i):
#     con.acquire()
#     con.wait()  # 在等待钥匙,
#     print("在第%s个循环里"%i)
#     con.release()
#
#
# con = Condition()
# for i in range(10):
#     Thread(target=func,args=(con,i)).start()
#
#
# while True:
#     num = int(input(">>>"))
#     con.acquire()
#     con.notify(num)  # 制造钥匙,
#     con.release()



# 定时器:
def func():
    print("时间同步")


Timer(2,func).start()  # 等待两秒钟开启一个线程,

###############    线程,队列  ##############

# 线程,队列

# 加锁会自己写代码,不方便,我们可以使用队列
# 队列内置了很多的锁,可以保证数据安全,

import queue

q = queue.Queue()

q.put()
q.put_nowait()  # 这个会报错
q.get()
q.get_nowait()  # 这个会报错,

# 队列的特点:先进先出,


queue.LifoQueue()  # 栈,先进后出,
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

queue.PriorityQueue()  # 优先级队列,
# 放数据的视乎,除了数据,还需要一个参数:优先级
q.put(20,1)
q.put(10,2)  # 数字越小,优先级越高,优先级一样,就是按照和ASk码排,
q.put(30,3)
print(q.get())
print(q.get())
print(q.get())


# 总结:
# 1,普通队列
# 2,栈
# 3,优先级队列,
# 这三种都不会出现多线程抢占资源,

###############   线程池    ##############

# 线程池


# 新的一个模块,
# 1 介绍
# concurrent.futures模块提供了高度封装的异步调用接口
# ThreadPoolExecutor:线程池,提供异步调用
# ProcessPoolExecutor: 进程池,提供异步调用


from concurrent.futures import ThreadPoolExecutor
import time
def func(n):
    time.sleep(2)
    print(n)
    return n*n


tpool = ThreadPoolExecutor(max_workers=5)
# 进程池,启动cpu核数+1.
# 而线程池的启动是cpu核数 * 5 不要超过这个,

t_list = []
for i in range(20):
    t = tpool.submit(func,i)  # 提交一个任务,传递一个参数,
    t_list.append(t)


tpool.shutdown()
# shutdown做了两个事情:
# 1,colse 关闭这个池子,不让有任务进来,
# 2,join是阻塞,直到这个池子的任务执行完,
# 所以是一个shutdown做了两个事情,
print("主进程")
for t in t_list:print(t.result())

###############    进程和线程的对比    ##############

进程和线程的对比:

面试的时候会问到,
进程是很多资源的总称,包括代码,包括显示器,包括鼠标,键盘等
线程是轻型的实现多任务的方式,
qq程序的多开,就是多个进程,
一个进程里面可以多个任务,这就是线程,
----------------------------
进程先有的,然后才有线程,
进程是一个资源分配的单位,具体谁拿着资源去做的呢,是线程,
一个进程里面一定有一个主线程,
--------------
所以多线程能实现多任务,是指的在一个进程里面有多个线程,实现多任务,
多进程的多任务,是有开辟了一个资源分配的单位,实现多任务,这个时候一个进程里面可能只有一个主线程,
真正执行代码的时候还是线程,
-------------
所以进程需要很大的资源才可以实现多任务,但是线程只需要很少的资源就可以实现多任务,
就像是同一个流水线,然后有很多的工人,
流水线就是进程,流水线上的工人就是一个线程,
所以开发的时候最长使用的是线程,这个比较轻,
------------
比如一个网易云音乐,既要下载音乐,又要播放音乐,
这就是一个进程,两个线程,
进程之间是独立的,
线程是依赖于进程的,没有进程就没有线程,
----------------------
所以这是非常的重要的,

###############    网络编程    ##############

相关推荐