多线程

一、多线程:3W1H

1、什么是多线程
不是严格意义上的并行,但是可以理解为并发。在极短的(比如一纳秒)时间内切换任务,让用户感受不到这个超快的切换过程,进而误以为是在同时处理
(并行处理:严格意义上的同一时刻处理多个任务,靠多路CPU或多核CPU)

2、为什么需要多线程
更有效的利用CPU资源,进而提升运算效率。结果就是线程越多,CPU使用率越高,搞到一定程度,%100,则CPU资源饱和

3、什么时候使用多线程
想要提高效率的时候,想要进行并发处理的时候

4、如何在Python中国使用多线程
方法1:直接定义一个类,并继承threading.Thread类,并重写父类方法run
方法2:直接实例化threading.Thread类,并传递要进行多线程运行的函数或方法以及对应的参数(推荐)

二、实例讲解

1、先看一个单线程

import threading,time

class threadingdemo(threading.Thread):

    def run(self):
        time.sleep(1)
        now = time.strftime("%Y-%m-%d %H:%M:%S")
        print(self.name+":"+now)

if __name__ == "__main__":
    print("开始搬砖")
    demo = threadingdemo()  
    for i in range(10):
        demo.run()
    print("搬砖结束")


运行的结果如果:
开始搬砖
Thread-1:2019-12-23 23:02:55
Thread-1:2019-12-23 23:02:56
Thread-1:2019-12-23 23:02:57
Thread-1:2019-12-23 23:02:58
Thread-1:2019-12-23 23:02:59
Thread-1:2019-12-23 23:03:00
Thread-1:2019-12-23 23:03:01
Thread-1:2019-12-23 23:03:02
Thread-1:2019-12-23 23:03:03
Thread-1:2019-12-23 23:03:04
搬砖结束
(for循环的十条会一条一条的打印出来,且可以看到线程名都是同一条)

2、多线程

  • 1、方法一:
class threadingdemo(threading.Thread):
    #重写父类的方法,以完成多线程调用
    def run(self):
        time.sleep(1)
        now = time.strftime("%Y-%m-%d %H:%M:%S")  #线程的运行,完全是运气,没有规律,不按顺序
        print(self.name+":"+now)

if __name__ == "__main__":
    print("开始搬砖")
    for i in range(10):
        demo = threadingdemo()     #多线程的时候,实例化也要多个实例化,才能更好的模拟多用户的情况
        demo.start()     #每一次循环,新建一个线程来运行,进而达到10个线程同时并发运行的效果
    print("搬砖结束")

运行结果:
开始搬砖
搬砖结束
Thread-1:2019-12-23 22:58:50
Thread-2:2019-12-23 22:58:50
Thread-3:2019-12-23 22:58:50
Thread-4:2019-12-23 22:58:50
Thread-6:2019-12-23 22:58:50
Thread-5:2019-12-23 22:58:50
Thread-9:2019-12-23 22:58:50
Thread-8:2019-12-23 22:58:50
Thread-7:2019-12-23 22:58:50
Thread-10:2019-12-23 22:58:50for循环的10个同时运行)
  • 2、方法二:
import threading,time

def print_one(x,y):
    for i in range(2):     #单线程,循环了2次
        time.sleep(1)
        new_time = time.strftime("%Y-%m_%d %H-%M-%S")
        print(threading.current_thread().name+":"+new_time)

def print_two():
    time.sleep(1)
    new_time = time.strftime("%Y-%m_%d %H-%M-%S")
    print(threading.current_thread().name+":"+new_time)

for i in range(2):                             #多线程,并发2个线程
    # threading.Thread(target=print_two).start()
    t = threading.Thread(target=print_one,args=(100,200))      #通过实例化Thred类时,传递构造参数完成多线程处理
    # t = threading.Thread(target=print_two)          #不需要传递参数,target接参数的时候,print_two不要加括号
    t.start()

time.sleep(1)
print("当前线程为:"+threading.current_thread().getName())        #主线程、可以在电脑的资源监控查看Python的进程使用情况

结果:
Thread-2:2019-12_23 23-17-05
Thread-1:2019-12_23 23-17-05
当前线程为:MainThread
Thread-1:2019-12_23 23-17-06
Thread-2:2019-12_23 23-17-06

(由结果可以看得出来,主线程不一定最先运行或者最后运行 )

3、线程池
演示:

import threading,time

def print_two():
    time.sleep(1)
    new_time = time.strftime("%Y-%m_%d %H-%M-%S")
    print(threading.current_thread().name+":"+new_time)

for i in range(4):                    #此方式打印出并没有实现并发的效果
    t = threading.Thread(target=print_two)
    t.start()
    t.join()   #将子线程合并到主线程,进而实现主线程一定要等待所有子线程结束后,才能结束自己,join之后就成了单线程

time.sleep(1)
print("当前线程为:"+threading.current_thread().getName())        #主线程、可以在电脑的资源监控查看Python的进程使用情况

运行结果:
Thread-1:2019-12_23 23-23-31
Thread-2:2019-12_23 23-23-32
Thread-3:2019-12_23 23-23-33
Thread-4:2019-12_23 23-23-34
当前线程为:MainThread
(行程并没有同时并发,而是一条一条运行)

优化后代码:

import threading,time
from concurrent.futures import ThreadPoolExecutor  #导入线程池


def print_two():
    time.sleep(1)
    new_time = time.strftime("%Y-%m_%d %H-%M-%S")
    print(threading.current_thread().name+":"+new_time)

#创建一个线程池,里面最多可以放5个线程
with ThreadPoolExecutor(5,"线程") as pool:   #"线程"是给的线程名字
    for i in range(6):  ##如果进程数为6个,则需要等待前面5个进程跑完,才能运行后面的1个。
        pool.submit(print_two)

print("当前线程为:"+threading.current_thread().getName())        #主线程、可以在电脑的资源监控查看Python的进程使用情况
																#使用线程池,主线程会等待


结果:
线程_1:2020-01_17 22-09-05
线程_0:2020-01_17 22-09-05
线程_3:2020-01_17 22-09-05
线程_2:2020-01_17 22-09-05
线程_4:2020-01_17 22-09-05
线程_1:2020-01_17 22-09-06
当前线程为:MainThread

(由线程名看到出来,for循环设置的6个线程,是先运行了5之后在运行的剩下的一个)

线程池的优点:

  • 1、可以控制产生线程的数量,通过预先创建一定数量的工作线程并限制其数量,控制线程对象的内存消耗
  • 2、降低系统开销和资源消耗,通过对多个请求重用线程,线程创建,销毁的开销被分摊到多个请求上。
  • 3、提高新系统响应速度,线程事先已被创建,请求到达时可直接处理,消除了因线程创建所带来的延迟,另外线程可以并发处理

4、守护线程

import threading,time

def print_two():
    time.sleep(1)
    new_time = time.strftime("%Y-%m_%d %H-%M-%S")
    print(threading.current_thread().name+":"+new_time)

threat_list = []     #使用一个列表,实现并发
for i in range(5):
    t = threading.Thread(target=print_two)  #t为子线程
    threat_list.append(t)

for t in threat_list:
    t.setDaemon(True)   #放到start()前,将该线程设置为守护线程,一旦主线程结束,子线程将无法运行
    t.start()
 
time.sleep(0.999)
print("当前线程为:"+threading.current_thread().getName())        #主线程、可以在电脑的资源监控查看Python的进程使用情况

结果:
Thread-2:2019-12_23 23-36-36
Thread-3:2019-12_23 23-36-36
Thread-1:2019-12_23 23-36-36
Thread-4:2019-12_23 23-36-36
当前线程为:MainThread

(由结果可以看得出来,当主线程结束之后,线程5就没有运行了)

5、线程锁

import threading
import time


Q= 0
def loop1(n):
    global Q
    Q = Q+n
    Q = Q-n

def one(n):
    for i in range(1000000):
        loop1(n)

print(f"主线程{threading.current_thread().name}开始")
t1 = threading.Thread(target=one,args=(5,))
t2 = threading.Thread(target=one,args=(6,))
t3 = threading.Thread(target=one,args=(7,))

t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

print(Q)
print(f"主线程{threading.current_thread().name}结束")

结果:
主线程MainThread开始
-8
主线程MainThread结束

因为线程共享内存,共享变量,多个线程操作同一个变量,就会发抢占

优化后代码:

import threading
import time


Q= 0
def loop1(n):
    global Q
    Q = Q+n
    Q = Q-n

lock = threading.Lock()   #线程锁
def one(n):
    for i in range(1000000):
        lock.acquire()    #获取锁
        loop1(n)
        lock.release()    #释放锁

print(f"主线程{threading.current_thread().name}开始")
t1 = threading.Thread(target=one,args=(5,))
t2 = threading.Thread(target=one,args=(6,))
t3 = threading.Thread(target=one,args=(7,))

t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()

print(Q)
print(f"主线程{threading.current_thread().name}结束")


结果:
主线程MainThread开始
0
主线程MainThread结束

当使用线程锁,程序的效率会降低,因为需要排队,但是相对数据错误是划算的。

6、信号量

import threading,time,random


#信号量:给0个
semaphore = threading.Semaphore(0)
item = []

def consumption(): #消费着
    for i in range(3):
        print("消费者等待")
        #获取信号量,当信号量大于0时,就可以进行下一步,否则,等待
        semaphore.acquire()
        print(f"消费着通知:消费者编号{item.pop()}")


def producers(): #生产者
    global item
    for i in range(3):
        time.sleep(random.randint(0,2))  #随机睡眠0~2秒
        #生产一个商品
        item.append(random.randint(1,1000))  #随机生成一个数,放到列表
        print(f"生产者通知:生产者商品编号{item[-1]}")
        #释放信号量,会让内部的计数器加1,既每释放一次 threading.Semaphore(0)就会加1
        #当信号量为0,等待线程会被唤醒
        semaphore.release()



if __name__ == "__main__":
    t1 = threading.Thread(target=producers)
    t2 = threading.Thread(target=consumption)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("结束")

结果:
消费者等待
生产者通知:生产者商品编号367
消费着通知:消费者编号367
消费者等待
生产者通知:生产者商品编号928
消费着通知:消费者编号928
消费者等待
生产者通知:生产者商品编号105
消费着通知:消费者编号105
结束

(会先消费着等待,等待生成之后才消费,相对线程锁,线程锁等于一个一个的线程运行。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值