Python多线程/单线程

定义:
  • 进程:资源的集合,一个程序就是一个进程。
  • 线程:一个程序最小的运行单位。
import threading #引入线程模块
import time
def run():
    time.sleep(5)
    print('over')

start_time=time.time()

run()
run()
run()
run()

end_time=time.time()
print('run_time', end_time-start_time)

#结果:run_time=20.38954234234
上面这个例子是单线程执行的,运行时间是20多秒,如果使用多线程,则会并行执行,执行结果应该是5秒左右。

  • 主线程等待子线程 

方法一:想要让主线程等待添加的线程,需要先把创建的线程统一放到list里面,循环执行完,使用.join()方法,如下:

import threading
import time
def run():
    time.sleep(5)
    print('over')

start_time=time.time()
thread_list = []
for i in range(5):
    t=threading.Thread(target=run)  #实例化一个线程,target代表要指定执行什么,例如:target=run就是执行run()这个函数,指定函数名,不要加()
    #函数里面如果有参数,可以加上args,如:t=threading.Thread(target=run,args[1,2,3,]) ,有几个参数,就在[]里面写几个参数
    t.start() #启动这个线程
    thread_list.append(t) #把线程放到list里面

for thread in thread_list:
    thread.join() #主线程等待子线程
end_time=time.time()
print('run_time=', end_time-start_time) 

方法二:每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行,如下:

import threading
import time

threads = []
start_time2 = time.time()
def insert_db():
    time.sleep(3)
    print('insert_db over')
for i in range(3):
    t = threading.Thread(target=insert_db)
    t.start()
while threading.activeCount()!=1:
#每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行
    pass
end_time2 = time.time()
print('多线程执行的时间',end_time2 - start_time2)
print('锁门...')
  • 线程 单个启动 & 同时启动

import  threading

'''单个启动'''
for i in range(10):
    t = threading.Thread(target=func,args=[1])
    t.start()#start()在这里是一个一个线程拿过来,单个启动

'''同时启动启动'''
ts = []
for i in range(10):
    t = threading.Thread(target=func,args=[1])
    ts.append(t)#先10个线程放到一个list里面
for t in ts:
    t.start() #循环同时启动

练习分析:

import threading
import time
def insert_db():
    time.sleep(3)
    print('insert_db over')
start_time = time.time()
for i in range(3):
    t = threading.Thread(target=insert_db)
    t.start()
end_time = time.time()
print('多线程执行的时间',end_time - start_time)

结果:
多线程执行的时间 0.0010001659393310547
insert_db over
insert_db over
insert_db over

我们看到的结果是中运行时间是0.0010001659393310547,还不到一秒钟,而我们代码所设置的是最少也会需要3秒钟,为什么时间会是这么短呢?

——因为所得到的时间只是主线程执行完它的工作时间,并不包括子线程执行的额时间。 

压测练习:

import threading
import requests
def request():
    while True:
        r = requests.get('http://api.nnzhp.cn/api/user/stu_info?stu_name=%E7%8E%8B%E5%B0%8F%E6%9C%88')
        print(r.json())

for i in range(10):
    t = threading.Thread(target=request)
    t.start()

运行后会一直死循环,一直压测,可以加入压测的时间

单线程下载网页:

import requests,time,threading
from hashlib import md5
result_list = {}
def down_load_pic(url):
    req = requests.get(url)
    m = md5(url.encode()) #把url转换为二进制md5下
    file_name = m.hexdigest()+'.png' #拼接文件名
    with open(file_name ,'wb') as fw:
        fw.write(req.content)
    # return file_name
    result_list[file_name] = threading.current_thread()

url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']

start_time = time.time()
for url in url_list:
    down_load_pic(url)
end_time = time.time()

print(end_time - start_time)

#结果:4.439253807067871

多线程下载网页:

import threading
import requests
import hashlib
import time
def down_load(url):
    name = hashlib.md5(url.encode()).hexdigest() #把url使用md5转化为密文
    r = requests.get(url)
    with open('%s.jpg'%name,'wb') as fw:
        fw.write(r.content)

l = [
    'http://www.nnzhp.cn/wp-content/themes/QQ/images/logo.jpg',
    'http://www.nnzhp.cn/wp-content/uploads/2016/12/2016aj5kn45fjk5-150x150.jpg',
    'http://www.nnzhp.cn/wp-content/themes/QQ/images/thumbnail.png'
]

for i in l:
    t = threading.Thread(target=down_load,args=[i])
    #参数只有一个时,要使用()的方式来写,需要多加一个逗号,如:args=(i,)
    # args是存参数的,如果里面只有一个参数的话,一定要在这个参数后面加一个逗号,因为是保存在元组里,如果不加逗号,它会默认为是字符串   应该写成:args=(url,)
    t.start()

while threading.activeCount()!=1:
    pass
print('down load over...'
一个进程里面至少有一个线程,这个线程就是主线程。
主线程只是调度用的,它把子线程招来之后就完事了,因此如果要统计运行时间,必须要让主线程等待所有的子线程都执行完后再记录结束时间。
case_result = []

def run_case(case_name):
    print('run case..',case_name)
    case_result.append(  {case_name:'success'})
#多线程运行函数时,函数的返回值是拿不到的,所以定义一个list
#把函数运行的结果都存进去就ok了 
  • 查看当前有多少个线程:threading.activeCount()

import threading #引入线程模块
import time
def run():
    time.sleep(1)

for i in range(10):
    t = threading.Thread(target=run)
    t.start()

print('当前有%s个线程'%threading.activeCount()) #当前有几个线程

结果:当前有11个线程
为什么会是11个线程而不是10个线程呢?我们明明是循环启动了10个线程,结果怎么就是11个了呢?
因为本身有一个主线程在运行,好比是我们自己在干活,我们又找了10个人来一起干活,加上我们自己一共是11个人在干活。所有的程序默认就会有一个线程。

2、守护线程

  • 一旦主线程死掉,那么守护线程不管有没执行完事,全部结束,相当于你是一个国王(主线程),你有很多仆人(守护线程),仆人都是为你服务的,一旦你死了,那么你的仆人都需要给你陪葬。
import threading
import time
def talk(name):
    print('正在和%s聊天'%name)
    time.sleep(200)

print("qq主窗口")
t = threading.Thread(target=talk,args=['刘一'])
t.setDaemon(True) #设置线程为守护线程
t.start()

time.sleep(5)

print('结束。。。')

结果:
qq主窗口
正在和刘一聊天
结束。。。

如果是没有设置为守护线程,本身程序执行需要有200秒才可以结束,设置了守护线程后,主线程5秒到了就会结束,主线程结束了,剩下的正在执行的线程已经是守护线程了,不会继续200秒结束,会立即跟随主线程结束。 

3、线程锁

  • 多个线程同时在操作同一个数据的时候,会有问题,就要把这个数据加个锁,然后同一时间只能有一个线程操作这个数据了【多个人或是多线程在同时操作同一个数据时,可能会有问题,可以加下锁】

  • 忘记了解锁或是代码运行时没有处理异常而没有走到解锁那里,都会成为线程死锁。

举例理解:比如说我们家里的卫生间,男女共用的,如果你进去时,没有锁门,有可能会有其他的开门,所以你需要进去时把门锁一下,不用了,再把锁打开下。

import threading
        from threading import Lock
        num = 0
        lock = Lock()#申请一把锁
        def run():
            global num
            lock.acquire(timeout = 3)#加锁,timeout是指定下时间,可以不写,如果指定了时间,超过后申请的锁就会失效
            num+=1
            lock.release()#解锁

        lis = []
        for i in range(5):
            t = threading.Thread(target=run)
            t.start()
            lis.append(t)
        for t in lis:
            t.join()
        print('over',num)
#多个线程操作同一个数据的时候,就得加锁
import threading

num = 0
lock = threading.Lock() #申请一把锁,实例化一把锁

def add():
    global num
    # lock.acquire()#加锁
    # num+=1
    # lock.release()#解锁  #不解锁,就会产生线程死锁,会一直在等待
    with lock:#简写,用with也会帮你加锁,解锁
        num+=1

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

while threading.activeCount() !=1:
    pass
  •  查看当前哪个线程在执行:threading.current_thread()

import threading
count = 0
lock = threading.Lock()
def test():
    global count
    print(threading.current_thread())
    lock.acquire()#加锁
    count+=1
    lock.release()#解锁
    #线程死锁

for i in  range(3):
    t = threading.Thread(target=test)
    t.start()

结果:
<Thread(Thread-1, started 9000)>
<Thread(Thread-2, started 4820)>
<Thread(Thread-3, started 10224)>

下面来个简单的爬虫,看下多线程的效果:

import threading
import requests, time

urls = {
    "baidu": 'http://www.baidu.com',
    "blog": 'http://www.nnzhp.cn',
    "besttest": 'http://www.besttest.cn',
    "taobao": "http://www.taobao.com",
    "jd": "http://www.jd.com",
}

def run(name, url):
    res = requests.get(url)
    with open(name + '.html', 'w', encoding=res.encoding) as fw:
        fw.write(res.text)

start_time = time.time()
lis = []
for url in urls:
    t = threading.Thread(target=run, args=(url, urls[url]))
    t.start()
    lis.append(t)
for t in lis:
    t.join()
end_time = time.time()
print('run time is %s' % (end_time - start_time))

#下面是单线程的执行时间
        # start_time = time.time()
        # for url in urls:
        #     run(url,urls[url])
        # end_time = time.time()
        # print('run time is %s'%(end_time-start_time))

4、多进程,上面说了Python里面的多线程,是不能利用多核CPU的,如果想利用多核CPU的话,就得使用多进程,python中多进程使用multiprocessing模块。

  • 一个电脑有几核的CPU,就只能同时运行几个任务

       ——比如说我们的电脑是4核的CPU,只可以同时运行4个进程,但我们在实际使用中,如果是4核的CPU,运行的并不止是4个程序,这是因为CPU上下文切换的,这个程序运行完了, 

              会立即切换到另外一个运行,我们感觉不到。

import multiprocessing,time

lock = multiprocessing.Lock() #申请一把锁
a = 1

def test():
    pass

def down_load():
    for i in range(5):
        t = threading.Thread(target = test)#启动多进程的同时,可以在启动多个多线程
        time.sleep(3)
        global a
        with lock: #使用with这个方法会自动的去判断使用完了自动的关掉锁释放【with是自动管理上下文的,操作文件时也可以使用,会自动识别关闭】
            a+=1
        print("运行完了")

if __name__ == '__main__':
    for i in range(5):
        p = multiprocessing.Process(target=down_load)
        # p=multiprocessing.Process(target=down_load,args =[1,2],name='brf') #有参数了可以加上args,可以定义进程名字
        p.start()
        # print(p.pid()) 查看进程ID

    while len(multiprocessing.active_children())!=0:#等待子进程结束,【multiprocessing.active_children()】是查看这个进程里面存活着几个子线程
        pass
               
print(multiprocessing.current_process()) 
print('end')

5、进程池

还可以使用进程池来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数,咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。【数据大时使用】

from multiprocessing import Pool
import os
 
 
def worker(msg):
    print("%s开始执行,进程号为%d" %  (msg,os.getpid()))
 
if __name__ == '__main__':
    
    po = Pool(3)  # 定义一个进程池,最大进程数3
    for i in range(0, 10):
        # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(func=worker,args=(i,))
        #第一个func参数指定运行的函数,第二个args是参数,没有参数可以不写
    print("----start----")
    po.close()  # 关闭进程池,关闭后po不再接收新的请求
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")

 总结:

  • 线程是用来干活的,只有进程的话是没办法运行的,进程里其实是线程在具体干活的。

  • 线程和线程之间是互相独立的。

  • 线程是在进程里面

  • 默认,一个进程里面只有一个进程

  • 进程相当于是一个工厂,线程相当于是工厂里面具体干活的一个人

  • 主线程,也就是程序一开始运行的时候,最初的那个线程

  • 子线程,通过thread类实例化的线程,都是子线程

  • 主线程等待子线程,执行结束后,主线程再去做别的操作

  • 主线程执行完它自己工作的时间,并不包括子线程执行的时间

  • Python里面的多线程利用不了多核的cpu,因为如果是在多个CPU上运行,运行的结果会不太一样,所以加入一个全局解释器锁(GLI),保证线程都在同一个cpu运行
  • 多进程可以利用多核CPU
  • CPU密集型任务,用多进程  ->消耗的CPU比较多
  • IO(磁盘IO、网络IO)密集型任务,用多线程   ->消耗IO比较多【IO是上传、下载的意思】:磁盘IO、网络IO
  • 多线程,线程之间的的数据是共享的,每个线程都可以操作里面的数据
  • 多进程,每个进程之间的数据是独立的,正是因为每个进程都是独立的,所以多进程里面加锁是没任何意义的
  • GLI 全局解释器所--->保证线程都在同一个cpu上运行
  • 【协程】:一个线程,速度很快,从头到尾只有一个线程在运行,不会再起线程;原理:只有一个线程,不再起线程,不去多浪费资源,任务来了,主要利用了异步IO,一个线程来回的切换,性能很好;如:Nginx就是使用了协程

扩展:

协程,只有有个线程在运行,每遇到消耗IO的地方就会立马切换,在切换到另一个,等着这个IO结束了,再去拿到数据,性能比较好;如:nginx就是使用的协程。


任何付出都是值得的,会越来越好  

原文地址:https://www.cnblogs.com/brf-test/p/11901937.html