操做系统,进程和线程

 

                         操做系统介绍                             

      操做系统管理计算机的硬件设备,使应用软件能方便、高效地使用这些设备。在微机上常见的有:DOS、WINDOWS、UNIX、OS/2等。
      在计算机软件中最重要且最基本的就是操做系统(OS)。它是最底层的软件,它控制全部计算机运行的程序并管理整个计算机的资源,是计算机裸机与应用程序及用户之间的桥梁。没有它,用户也就没法使用某种软件或程序。
 
     操做系统是计算机系统的控制和管理中心,从资源角度来看,它具备处理机、存储器管理、设备管理、文件管理等4项功能。
     经常使用的系统有DOS操做系统、WINDOWS操做系统、UNIX操做系统和Linux、Netware等操做系统 .

    细分的话,操做系统应该分红两部分功能:html

#一:隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操做硬件的细节,专心开发本身的应用程序便可。
例如:操做系统提供了文件这个抽象概念,对文件的操做就是对磁盘的操做,有了文件咱们无需再去考虑关于磁盘的读写控制(好比控制磁盘转动,移动磁头读写数据等细节),

#二:将应用程序对硬件资源的竞态请求变得有序化
例如:不少应用软件实际上是共享一套计算机硬件,比方说有可能有三个应用程序同时须要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,而后多是b竞争到打印机资源,也多是c,这就致使了无序,打印机可能打印一段a的内容而后又去打印c...,操做系统的一个功能就是将这种无序变得有序。

 

                               进程                                    

          什么是进程          

   进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在当代面向线程设计的计算机结构中,进程是线程的容器.程序是指令,数据及其组织形式的描述,进程是程序的实体.python

    注意 : 同一个程序执行两次,就会在操做系统中出现两个进程,因此咱们能够同时运行一个软件,分别作不一样的事情也不会混乱.linux

         进程的并行与并发        

  并行 : 并行指二者同时进行 , 好比跑赛,两我的都在不停的向前跑 : (资源够用)
程序员

  并发 : 并发指在资源有限的状况下,二者交替轮流使用资源.好比一张大饼(单核CPU),两我的都很是饿,可是吃的时候只能有一我的吃,那么就是A吃一口,让给B,B再吃一口然给A,交替使用,目的是提升效率.面试

   区别 : 编程

   并行 : 并行是从微观上,也就是说在一个精确的时间片刻,有不一样的程序在执行,这就要求必须有多个处理器安全

   并发 : 并发是从宏观上,在一个时间段里能够看出是同时执行的,好比一个服务器同时处理多个程序.(好比,一遍看视频,一遍聊天.)服务器

 

 

                       Python中的进程操做                

在Windows操做系统中因为没有fork(linux操做系统中建立进程的机制),在建立子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。所以若是将process()直接写在文件中就会无限递归建立子进程报错。因此必须把建立子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
在Windows上使用进程的注意事项

          进程的建立          网络

1 p.daemon:默认值为False,若是设为True,表明p为后台运行的守护进程,当p的父进程终止时,p也随之终止,而且设定为True后,p不能建立本身的新进程,必须在p.start()以前设置
2 p.name:进程的名称
3 p.pid:进程的pid / ident---进程的id
4 p.exitcode:进程在运行时为None、若是为–N,表示被信号N结束(了解便可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络链接的底层进程间通讯提供安全性,这类链接只有在具备相同的身份验证键时才能成功(了解便可)
6 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,须要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
属性介绍

            基本操做       多线程

import multiprocessing

def func(arg):
    print(arg)

if __name__ == '__main__':     
    for i in range(10):
        t = multiprocessing.Process(target=func,args=(i,))
        t.start()

       ●       进程间的数据不共享     

import multiprocessing

li = []
def func(arg):
    li.append(arg)
    print(li)


def run():
    for i in range(10):
        t = multiprocessing.Process(target=func,args=(i,))
        t.start()

if __name__ == '__main__':
    run()

结果:    每个数都是一个进程
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]

   ●    进程通用功能  -- name         

import multiprocessing
import time

def func(arg):
    time.sleep(2)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    # p1.name = "pig"     #进程名称
    # print(p1.name)
    p1.start()
    print("888")

    p2 = multiprocessing.Process(target=func,args=(2,))
    # p2.name = "dog"
    
    p2.start()
    print("999")

if __name__ == '__main__':
    run()

   ●开发者能够控制主进程等待子进程(最多等待时间)  join()                     

import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    p1.start()
    # p1.join(1)
    print("888")
    p1.join(1)

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.start()
    # p1.join(1)
    print("999")
    p2.join(1)

if __name__ == '__main__':
    run()

结果:
666
888
#2秒后
999
#5秒后
1
2
join方法            

           注意:join() :   

    无参数,让主线程在这里等着,等到子线程t1执行完毕,才能够继续往下走。
    有参数,让主线程在这里最多等待n秒,不管是否执行完毕,会继续往下走。

    ● 守护进程   Daemon

      会随着主进程的结束而结束

      主进程建立守护进程

    其一:守护进程会在主进程代码执行结束后就终止

    其二:守护进程内没法再开启子进程,不然抛出异常:AssertionError: daemonic processes are not allowed to have children

    注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

import os
import time
from multiprocessing import Process

class Myprocess(Process):
    def __init__(self,person):
        super().__init__()
        self.person = person
    def run(self):
        print(os.getpid(),self.name)
        print('%s正在和女主播聊天' %self.person)


p=Myprocess('托塔')
p.daemon=True #必定要在p.start()前设置,设置p为守护进程,禁止p建立子进程,而且父进程代码执行结束,p即终止运行
p.start()
time.sleep(10) # 在sleep时查看进程id对应的进程ps -ef|grep id
print('')
守护进程的启动
import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,)) #daemon 也能够放这里
    p1.daemon = True 
    p1.start()
    print("888")

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.daemon = True
    p2.start()
    print("999")


if __name__ == '__main__':
    run()

        ● 获取id     pid / ident           

import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    p1.start()
    print(p1.pid)

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.start()
    print(p2.pid)

if __name__ == '__main__':
    run()

结果:
666
4808
15240
1
2

           类继承的方式               

class MyProcess(multiprocessing.Process):

    def run(self):
        print('当前进程',multiprocessing.current_process())


def run():
    p1 = MyProcess()
    p1.start()

    p2 = MyProcess()
    p2.start()

if __name__ == '__main__':
    run()

结果:
当前进程 <MyProcess(MyProcess-1, started)>
当前进程 <MyProcess(MyProcess-2, started)>

 

       进程间的数据共享          

Queue

  建立共享的进程队列,Queue是多进程安全的队列,可使用Queue实现多进程之间的数据传递.

Queue([maxsize]) 
建立共享的进程队列。
参数 :maxsize是队列中容许的最大项数。若是省略此参数,则无大小限制。
底层队列使用管道和锁定实现。
Queue([maxsize]) 
建立共享的进程队列。maxsize是队列中容许的最大项数。若是省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还须要运行支持线程以便队列中的数据传输到底层管道中。 
Queue的实例q具备如下方法:

q.get( [ block [ ,timeout ] ] ) 
返回q中的一个项目。若是q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 若是设置为False,将引起Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。若是在制定的时间间隔内没有项目变为可用,将引起Queue.Empty异常。

q.get_nowait( ) 
同q.get(False)方法。

q.put(item [, block [,timeout ] ] ) 
将item放入队列。若是队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。若是设置为False,将引起Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引起Queue.Full异常。

q.qsize() 
返回队列中目前项目的正确数量。此函数的结果并不可靠,由于在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引起NotImplementedError异常。


q.empty() 
若是调用此方法时 q为空,返回True。若是其余进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。

q.full() 
若是q已满,返回为True. 因为线程的存在,结果也多是不可靠的(参考q.empty()方法)。。

方法介绍
方法介绍

 示例 :  

import multiprocessing
q = multiprocessing.Queue()

def task(arg, q):
    q.put(arg)

def run():
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, q,))
        p.start()

    while True:
        v = q.get()
        print(v)
run()
在linux系统
import multiprocessing


def task(arg, q):
    q.put(arg)


if __name__ == '__main__':
    q = multiprocessing.Queue()
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, q,))
        p.start()
    while True:
        v = q.get()
        print(v)

结果:
0
1
2
3
4
5
6
7
8
9
在Windows系统

 

 Manager   ***

进程间数据是独立的,能够借助于队列或管道实现通讯,两者都是基于消息传递的
虽然进程间数据独立,但能够经过Manager实现数据共享,事实上Manager的功能远不止于此

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
Manager模块介绍

 示例 : 

import multiprocessing
import time

def task(arg, dic):
    time.sleep(2)
    dic[arg] = 100


if __name__ == '__main__':
    m = multiprocessing.Manager()
    dic = m.dict()

    process_list = []
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, dic,))
        p.start()

        process_list.append(p)

    while True:
        count = 0
        for p in process_list:
            if not p.is_alive():
                count += 1
        if count == len(process_list):
            break
    print(dic)

结果:
{0: 100, 1: 100, 2: 100, 3: 100, 4: 100, 5: 100, 6: 100, 7: 100, 8: 100, 9: 100}
在Windows系统中
import multiprocessing

m = multiprocessing.Manager()
dic = m.dict()
def task(arg):
    dic[arg] = 100

def run():
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i,))
        p.start()

    input('>>>')
    print(dic.values())


if __name__ == '__main__':
    run()
在Linux系统

 

       进程锁        

       进程锁与线程锁的方法是同样的,也是有Lock , RLock , Semaphore , Event , Condition.

     RLock       

import time
import threading
import multiprocessing


lock = multiprocessing.RLock()

def task(arg):
    print("我真帅")
    lock.acquire()
    time.sleep(2)
    print(arg)
    lock.release()


if __name__ == '__main__':
    p1 = multiprocessing.Process(target=task,args=(1,))
    p1.start()

    p2 = multiprocessing.Process(target=task, args=(2,))
    p2.start()

结果:
我真帅
我真帅
1
2

 

 

         进程池         

   为何要有进程池?进程池的概念。

   在程序实际处理问题过程当中,忙时会有成千上万的任务须要被执行,闲时可能只有零星任务。那么在成千上万个任务须要被执行的时候,咱们就须要去建立成千上万个进程么?首先,建立进程须要消耗时间,销毁进程也须要消耗时间。第二即使开启了成千上万的进程,操做系统也不能让他们同时执行,这样反而会影响程序的效率。所以咱们不能无限制的根据任务开启或者结束进程。那么咱们要怎么作呢?

   定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等处处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。若是有不少任务须要执行,池中的进程数量不够,任务就要等待以前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增长操做系统的调度难度,还节省了开闭进程的时间,也必定程度上可以实现并发效果。

import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def task(arg):
    time.sleep(2)
    print(arg)

if __name__ == '__main__':

    pool = ProcessPoolExecutor(5)
    for i in range(10):
        pool.submit(task,i)

结果:
#2秒后
0
2
1
3
4
#2秒后
5
7
6
8
9

  因此,咱们能够利用进程池,写一个小爬虫的实例:

import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_page(url,pattern):
    response=urlopen(url).read().decode('utf-8')
    return pattern,response

def parse_page(info):
    pattern,page_content=info
    res=re.findall(pattern,page_content)
    for item in res:
        dic={
            'index':item[0].strip(),
            'title':item[1].strip(),
            'actor':item[2].strip(),
            'time':item[3].strip(),
        }
        print(dic)
if __name__ == '__main__':
    regex = r'<dd>.*?<.*?class="board-index.*?>(\d+)</i>.*?title="(.*?)".*?class="movie-item-info".*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>'
    pattern1=re.compile(regex,re.S)

    url_dic={
        'http://maoyan.com/board/7':pattern1,
    }

    p=Pool()
    res_l=[]
    for url,pattern in url_dic.items():
        res=p.apply_async(get_page,args=(url,pattern),callback=parse_page)
        res_l.append(res)

    for i in res_l:
        i.get()
猫眼---爬虫

 

 

                               线程                               

          进程           

       程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,咱们容许多个程序同时加载到内存中,在操做系统的调度下,能够实现并发地执行。这是这样的设计,大大提升了CPU的利用率。进程的出现让每一个用户感受到本身独享CPU,所以,进程就是为了在CPU上实现多道编程而提出的。

     线程的出现缘由       

      进程有不少优势,它提供了多道编程,让咱们感受咱们每一个人都拥有本身的CPU和其余资源,能够提升计算机的利用率。可是进程仍是有不少缺陷的,主要体如今两点上:

  • 进程只能在一个时间干一件事,若是想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程当中若是阻塞,例如等待输入,整个进程就会挂起,即便进程中有些工做不依赖于输入的数据,也将没法执行

  好比:若是把咱们在写代码的时候想要写代码的时候能够听歌和聊天.可是若是只是提供进程这个机制的话,那么咱们就只能写完代码,在听歌,而后再聊天,不能再同时进行.

  因此咱们要解决这个问题,咱们彻底可让写代码,听歌,聊天三个独立的过程,并行起来,这样很明显能够提升效率。而实际的操做系统中,也一样引入了这种相似的机制——线程。

 

      线程的出现       

       60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了不少弊端,一是因为进程是资源拥有者,建立、撤消与切换存在较大的时空开销,所以须要引入轻型进程;二是因为对称多处理机(SMP)出现,能够知足多个运行单位,而多个进程并行开销过大。
  所以在80年代,出现了能独立运行的基本单位——线程(Threads)
  注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
     每个进程中至少有一个线程。 

       线程和进程的关系          

      线程与进程的区别 : 

    ● 地址空间和其余资源(好比打开文件) : 进程之间相互独立,同一进程的各线程之间共享资源.某进程内的线程和其余进程内的线程是不可见的.

    ● 通讯 : 进程间通讯IPC,线程间能够直接读写进程数据段(如全局变量)来进行通讯(--须要进程同步和互斥手段的辅助,以保证数据的一致性).

    ● 调度和切换 : 线程上下文切换比进程上下文切换要快的多.

    ● 在多线程操做系统中,进程不是一个可执行的实体.

              ***经过一个漫画了解线程和进程(网上找的)***

       进程和线程的区别(面试):

     1.进程是CPU分配的最小单元.

        线程是CPU计算的最早单元.

        2.一个进程能够有多个线程.

                  3.对于Python来讲,它的进程和线程和其余语言有差别,是有GIL锁的

      GIL锁保证一个进程中同一时刻只有一个线程被CPU调度.

        线程的特色          

    在多线程的操做系统中,一般是在一个进程中包括多个线程,每个线程都是做为利用CPU的基本单元,是花费最小开销的实体.

     线程的属性 : 

    ● 轻型实体

      线程上的实体基本上不拥有系统资源,只是有一点必不可少的,能保证独立运行的资源.

      线程的实体包括程序,数据和TCB.线程是动态概念,他的动态特性由线程控制块TCB描述.

TCB包括如下信息:
(1)线程状态。
(2)当线程不运行时,被保存的现场资源。
(3)一组执行堆栈。
(4)存放每一个线程的局部变量主存区。
(5)访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
TCB包括如下信息

    ● 独立调度和分派的基本单位.

      在多线程OS中,线程是能独立运行的最小单位,于是也是独立调度和分派的基本单位,因为现场很"轻",故线程的切换很是迅速且开销小(在同一进程中的线程.)

    ● 共享进程资源

      线程在同一进程中的各个线程,均可以共享该进程所拥有的资源,这首先表如今 : 全部线程都具备相同的进程id,意味着,线程能够访问该进程的每个内存资源:此外,还能够访问进程所拥有的已经打开文件,定时器,信号量机构等.因为同一个进程内的线程共享内存和文件,全部线程直接互相通讯没必要调用内核.

    ● 可并发执行

      在同一个进程的多个线程之间,能够并发执行,甚至容许在一个进程中全部线程都能并发执行:一样,不一样进程中的线程也能并发执行,充分利用和发挥了处理机制与外围设备并行工做的能力.

      使用线程的实际场景       

  

        开启一个字处理软件进程,该进程确定须要办不止一件事情,好比监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操做的都是同一块数据,于是不能用多进程。只能在一个进程里并发地开启三个线程,若是是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。

                           Python中的线程                        

      Python中本身是没有线程和进程的,Python是调用操做系统中的线程和进程.

          全局解释器---GIL锁             

        Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中能够“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,因此,GIL锁的做用就是保证一个进程中在同一时刻只要一个线程能够运行(被CPU调度).

       注意 : 默认GIL锁在执行100个CPU指令后换另一个线程执行.

      因此 , 因为在Python中存在一个GIL锁:

        ---致使 : 多线程没法利用多核的优点

        ---解决 : 开多进程处理(浪费资源)

       线程的建立          

#基本使用

import threading

def func(arg):
    print(arg)
t = threading.Thread(target=func,args=(112,))     #子线程
t.start()
print(123)                                        #主线程

结果:
112
123
● 主线程默认等子线程执行完毕
import threading
import time

def func(arg):
    time.sleep(arg)
    print(arg)

t1 = threading.Thread(target=func,args=(1,))
t1.start()

t2 = threading.Thread(target=func,args=(3,))
t2.start()
print("我真帅")

结果:
我真帅
#1秒后
1
#3秒后
3
主线程默认等子线程执行完毕

  ● 主线程再也不等,主线程终止则因此线程终止   setDaemon()

import threading
import time

def func(arg):
    time.sleep(2)
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
t1.setDaemon(True)      #使主线程不等子线程
t1.start()

t2 = threading.Thread(target=func,args=(5,))
t2.setDaemon(True)
t2.start()

print("我真帅")
View Code

  ●开发者能够控制主线程等待子线程(最多等待时间)  join()

import threading
import time

def func(arg):
    time.sleep(arg)
    print(arg)

print("我是有多帅")
t1 = threading.Thread(target=func,args=(3,))
t1.start()
t1.join()

print("很是帅")
t2 = threading.Thread(target=func,args=(5,))
t2.start()
t2.join()

print("大实话")

结果:
我是有多帅
#3秒后
3
很是帅
#5秒后
5
大实话
无参数
import threading
import time

def func(arg):
    time.sleep(arg)
    print(arg)

print("我是有多帅")
t1 = threading.Thread(target=func,args=(5,))
t1.start()
t1.join(2)

print("很是帅")
t2 = threading.Thread(target=func,args=(9,))
t2.start()
t2.join(2)

print("大实话")

结果:
我是有多帅
#2秒后
很是帅
#2秒后
大实话
#5秒后(和大实话差1秒)
5
#9秒后
9
有参数

     注意:join() :   

    无参数,让主线程在这里等着,等到子线程t1执行完毕,才能够继续往下走。
   有参数,让主线程在这里最多等待n秒,不管是否执行完毕,会继续往下走。
● 线程名称 srtName()
import threading

def func(arg):
    # 获取当前执行该函数的线程的对象
    t = threading.current_thread()
    # 根据当前线程对象获取当前线程名称
    name = t.getName()
    print(name,arg)

t1 = threading.Thread(target=func,args=(11,))
t1.setName("张三")            #使线程有名称
t1.start()

t2 = threading.Thread(target=func,args=(22,))
t2.setName('李四')
t2.start()

print(123)
线程名称
● 线程本质
import threading

def func(arg):
    print(arg)

t1 = threading.Thread(target=func,args=(66,))
t1.start()

# start 是开始运行线程吗?不是
# start 告诉cpu,我已经准备就绪,你能够调度我了。

print("我好看")

结果:
66
我好看
线程本质
● 面向对象的多线程
import threading

线程方式:1 (常见)
def func(arg):
    print(arg)

t1 = threading.Thread(target=func,args=(11,))
t1.start()

# 多线程方式:2
class MyThread(threading.Thread):

    def run(self):
        print(11111,self._args,self._kwargs)

t1 = MyThread(args=(11,))
t1.start()

t2 = MyThread(args=(22,))
t2.start()

print('end')
View Code
● IO操做
import threading
import requests
import uuid

url_list = [
    'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg',
    'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg',
    'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg',
]

def task(url):
    ret = requests.get(url)
    file_name = str(uuid.uuid4()) + '.jpg'
    with open(file_name, mode='wb') as f:
        f.write(ret.content)

for url in url_list:

    t = threading.Thread(target=task,args=(url,))
    t.start()
View Code

 

        总结 :
      
1.Python中存在一个GIL锁。
        ---形成:多线程没法利用多核优点
        ---解决:开多进程处理(浪费资源)

        2.IO密集型(文件的输入/输出/socket网络通讯) : 多线程 (不占用CPU)
        计算密集型 : 多进程
      3.为何Python有GIL锁?
       答 : Python语言创始人在开发这门语言时,目的是快速把语言开发出来,若是加上GIL锁(C语言加锁),切换时按照100条字节指令来进行线程间的切换.

        4.线程建立的越多越好吗?
       答:很差,线程之间进行切换时,要作上下文处理.

 

         锁           

          同步锁    Lock(一次放一个)     

    线程安全:多线程操做时,内部会让全部线程排队处理.如:list / dict / Queue

    线程不安全 + 人 => 排队处理

建立100个线程
    v = []
    加锁
    -把本身的添加到列表中.安全的
    -读取列表的最后一个.最后一个就不必定是本身的了,因此不安全
    解锁
需求说明

 

import threading
import time

v = []
lock = threading.Lock()
def func(arg):
    lock.acquire()     上锁
    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)
    lock.release()     解锁

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

   之后锁一个代码块 

 

 

           递归锁 RLock(一次放一个) 与死锁            

所谓死锁: 是指两个或两个以上的进程或线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,以下就是死锁.

import threading
import time

v = []
lock = threading.Lock()

def func(arg):
    lock.acquire()
    lock.acquire()

    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)

    lock.release()
    lock.release()

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

解决方法,递归锁,在Python中为了支持在同一线程中屡次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源能够被屡次require。直到一个线程全部的acquire都被release,其余的线程才能得到资源。上面的例子若是使用RLock代替Lock,则不会发生死锁:

import threading
import time

v = []
lock = threading.RLock()
def func(arg):
    lock.acquire()
    lock.acquire()

    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)

    lock.release()
    lock.release()


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

 

 

        信号量 BoundedSemaphore  (一次放n个)         

同进程的同样

BoundedSemaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其余线程调用release()。

实例:(同时只有3个线程能够得到BoundedSemaphore,便可以限制最大链接数为3):

import time
import threading

lock = threading.BoundedSemaphore(3)
def func(arg):
    lock.acquire()
    print(arg)
    time.sleep(2)
    lock.release()


for i in range(20):
    t =threading.Thread(target=func,args=(i,))
    t.start()

结果:
#2秒后
0
1
2
#2秒后
3
4
5
#2秒后
6
7
8
9
10
11
12
13
14
15
16
17
18
19

 

 

          条件 Condition (一次放x个,根据条件)           

        使得线程等待,只有知足某条件时,才释放n个线程

Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock相似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,而后判断一些条件。若是条件不知足则wait;若是条件知足,进行一些处理改变条件后,经过notify方法通知其余线程,其余处于wait状态的线程接到通知后会从新判断条件。不断的重复这一过程,从而解决复杂的同步问题。
说明

  代码说明: 

import time
import threading

lock = threading.Condition()

def func(arg):
    print('线程进来了')
    lock.acquire()
    lock.wait() # 加锁

    print(arg)
    time.sleep(1)

    lock.release()


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

while True:
    inp = int(input('>>>'))

    lock.acquire()
    lock.notify(inp)
    lock.release()

结果:
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
线程进来了
>>>8 #输入8. 由于谁也不知道CPU是先调用那个线程,因此没有顺序
>>>0
2
3
5
4
6
7
1
3  #输入2 可是只剩下2个
>>>8
9
实例1
import time
import threading

lock = threading.Condition()

def xxxx():
    print('来执行函数了')
    input(">>>")
    # ct = threading.current_thread() # 获取当前线程
    # ct.getName()
    return True

def func(arg):
    print('线程进来了')
    lock.wait_for(xxxx)
    print(arg)
    time.sleep(1)

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

#说明:先循环,调用func函数,打印"线程来了",等待调用函数xxxx,打印"来执行函数了",用户输入数字,返回arg = i ,因此不管输入什么都是一个线程执行.
实例2

 

 

         事件 Event (一次放行)           

同进程的同样

线程的一个关键特性是每一个线程都是独立运行且状态不可预测。若是程序中的其 他线程须要经过判断某个线程的状态来肯定本身下一步的操做,这时线程同步问题就会变得很是棘手。为了解决这些问题,咱们须要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它容许线程等待某些事件的发生。在 初始状况下,Event对象中的信号标志被设置为假。若是有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程若是将一个Event对象的信号标志设置为真,它将唤醒全部等待这个Event对象的线程。若是一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

event.isSet():返回event的状态值;
event.wait():若是 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,全部阻塞池的线程激活进入就绪状态, 等待操做系统调度;
event.clear():恢复event的状态值为False。

 

 

import time
import threading

lock = threading.Event()

def func(arg):
    print('线程来了')
    lock.wait()     # 加锁:红灯
    print(arg)

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

input(">>>>")
lock.set()      # 绿灯

lock.clear()    # 再次变红灯

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

input(">>>>")
lock.set()    #放行

 

                   总结 : 为何要加锁?

                              ---非线程安全

         ---控制一段代码块

 

        线程池     

   做用: 调用线程,规定并发的最大个数

from concurrent.futures import ThreadPoolExecutor
import time

def task(a1,a2):
    time.sleep(2)
    print(a1,a2)

# 建立了一个线程池(最多5个线程)
pool = ThreadPoolExecutor(5)

for i in range(40):
    # 去线程池中申请一个线程,让线程执行task函数。
    pool.submit(task,i,8)

 

      threading.local        

   当咱们在考试的时候,会有收手机这个行为,考场的老师会将咱们的手机放到一个收纳盒中,等考试结束以后,才能够拿本身的手机,可是这里有一个弊端,会不会有人看到 iPhone X 就拿走了.可是当咱们为每一个人都建立一个收纳盒,放本身的手机在写上名字,这样是否是就不会拿错了呢?

   因此 , threading.local 的做用 : 内部自动为每个线程维护一个空间(以字典形式),用于当前存取属于本身的值,保证数据隔离.

{
    线程ID: {...},
    线程ID: {...},
    线程ID: {...},
    线程ID: {...}
}

   示例 :  

import time
import threading

v = threading.local()

def func(arg):
    # 内部会为当前线程建立一个空间用于存储:phone=本身的值
    v.phone = arg
    time.sleep(2)
    print(v.phone,arg) # 去当前线程本身空间取值

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

结果:
0 0
1 1
4 4
2 2
5 5
3 3
6 6
7 7
9 9
8 8

 

"""
之后:Flask框架内部看到源码 上下文管理

"""

import time
import threading
INFO = {}
class Local(object):

    def __getattr__(self, item):
        ident = threading.get_ident()
        return INFO[ident][item]

    def __setattr__(self, key, value):
        ident = threading.get_ident()
        if ident in INFO:
            INFO[ident][key] = value
        else:
            INFO[ident] = {key:value}

obj = Local()

def func(arg):
    obj.phone = arg # 调用对象的 __setattr__方法(“phone”,1)
    time.sleep(2)
    print(obj.phone,arg)


for i in range(10):
    t =threading.Thread(target=func,args=(i,))
    t.start()
原理

 

 

      生产者消费者模型         

      当咱们在节假日或者春运的时候买车票,咱们买票的和卖票的之间存在着必定的关系,由于卖票的不知道是要多少人才能知足人买票的需求,这就须要排队.这样就会形成必定的事件.可是,当咱们在买票和卖票的之间放一根管道:

 

 这样,咱们能够把需求放入管道里,就能够去作别的事情,当票打印以后再通知你,这样就能够两不当误.咱们称管道叫作:队列.

       线程队列       

   queue队列: 使用 import queue

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

(队列在线程编程中特别有用,当信息必须在多个线程之间安全交换时。)

queue.Queue() #先进先出

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

结果(先进先出):
first
second
third

    示例 : 

import time
import queue
import threading
q = queue.Queue() # 线程安全

def producer(id):
    """
    生产者
    :return:
    """
    while True:
        time.sleep(2)
        q.put('包子')
        print('厨师%s 生产了一个包子' %id )

for i in range(1,4):
    t = threading.Thread(target=producer,args=(i,))
    t.start()


def consumer(id):
    """
    消费者
    :return:
    """
    while True:
        time.sleep(1)
        v1 = q.get()
        print('顾客 %s 吃了一个包子' % id)

for i in range(1,3):
    t = threading.Thread(target=consumer,args=(i,))
    t.start()