python并发编程(四)——线程(一)

线程

线程是CPU调度的最小单元
进程是资源分配的最小单位
每一个进程最少有一个线程
python 提供了几个用于多线程的模块(thread,threading,Queue等)
thread,threading提供了创建和管理线程的方法
thread提供了对基本线程和锁的支持,不支持守护线程
threading提供了级别更高,功能更强的线程管理
Queue允许用户创建一个基于多线程共享数据的队列数据结构

线程与进程的区别

进程和线程的主要区别在于它们是操作系统管理资源的不同方式
线程依赖于进程存在,有自己的堆栈和局部变量
单个进程中的多线程内存数据是共享的,多进程的数据是不共享的
多进程的程序要比多线程的程序健壮

为什么学线程

进程同一时间只能做一件事
进程一旦阻塞就会挂起不再执行
进程的创建,撤销,切换存在极大的资源开销

线程的使用

multiprocess模块完全模仿了threading模块,两者在使用上有极大的相似性
方式一:
创建线程:Thread(target=fun,args = (i,))
	fun——线程方法名
	(i,)——方法参数
启动线程:start()
等待线程对象结束:join()
方式二:继承Thread重写run方法
#创建10个线程并启动
from threading import Thread
import time 
from nt import getpid

def fun(n):
    print('子线程 的 进程号:',getpid())
    time.sleep(0.1)
    print(n)
    
if __name__ == '__main__':
    print('主线程 的 进程号:',getpid()) #主线程与子线程在同一个进程中
    for i in range(10):#开启10个线程
        t =Thread(target=fun,args = (i,))#创建线程对象,传递参数
        t.start()#启动线程对象
#创建并调用线程的方式一
# 主线程 的 进程号: 6440
# 子线程 的 进程号: 6440
# 子线程 的 进程号: 6440
#...
#...
# 0
# 1
# 5
# 4
# 3
# 2
# 9
# 7

from threading import Thread
import time 
class MyThread(Thread):
    def run(self):
        time.sleep(0.1)
        print(1)
    
if __name__ == '__main__':   
    #创建并调用线程的方式二
    t = MyThread()
    t.start()

线程中其它方法

threading.active_count()	获取所有的线程数
threading.current_thread() 获取当前线程对象的名字与id
threading.get_ident()	获取当前线程的id号
threading.enumerate()	获取所有线程对象组成的列表
import time
import threading
#threading.current_thread() 获取当前线程的名字与id
def wahaha(n):
    time.sleep(0.5)
    print(n,threading.current_thread(),threading.get_ident())

for i in  range(10):
    threading.Thread(target=wahaha,args=(i,)).start()
print(threading.active_count())    # 获取所有的线程数量(包括主线程)
print(threading.current_thread())# 主线程对象
print(threading.enumerate()) #获取所有线程对象组成的列表

#11  线程数为11
#<_MainThread(MainThread, started 8944)>	主线程对象
#[<_MainThread(MainThread, started 8944)>, ...] 线程对象组成的列表
#0 <Thread(Thread-1, started 1600)> 1600
#2 <Thread(Thread-3, started 8392)> 8392
#1 <Thread(Thread-2, started 6000)> 6000
#7 <Thread(Thread-8, started 7588)> 7588
#5 <Thread(Thread-6, started 8940)> 8940
#....

多线程与多进程的效率

同时开启10个线程和10个进程模拟文件下载
from multiprocessing import Process
from threading import Thread
import time

def downfile(s):
    print(s,'号文件开始下载')
    time.sleep(1)#休眠1秒
    print(s,'号文件下载结束')
开启多进程的效率	
if __name__ == '__main__':
    starttime = time.time()
    l = []
    for i in range(10):#开启10个进程
        p = Process(target=downfile,args=(i,))
        p.start()#启动进程
        l.append(p)
        
    for i in l:
        i.join()#等待子进程对象执行结束
        
    end = time.time() - starttime
    print('进程耗时 :',end)#进程耗时
# 0 号文件开始下载
# 1 号文件开始下载
# 3 号文件开始下载
#....
# 8号文件下载结束
# 6 号文件下载结束
# 进程耗时 : 1.4079999923706055
 print('---------------------------------------')
    starttime2 = time.time()   
    l = []
    for i in range(10):#开启10个线程
        t =Thread(target=downfile,args = (i,))#创建线程,传递参数
        t.start()#启动线程
        l.append(t)#添加线程对象
    
    for j in l:
        j.join()#等待线程执行结束
    end2 = time.time() - starttime2    
    print('线程耗时 :',end2)#线程耗时

#线程耗时 : 1.004000186920166

线程中的共享变量

线程中的共享变量源于同一个进程
使用进程中共享变量需要global关键字,表明这个变量是全局的


声明一个共享变量 g=100
开启10个子线程,通过global关键字对共享变量进行修改
from threading import Thread
#multiprocess模块完全模仿了threading模块,两者在使用上有极大的相似性
import time 
from nt import getpid
g = 100 #共享进程中的数据  
def fun(n):
    global g
    g -= 1
    print('%s号子线程 的 进程号:%s'%(n,getpid()))
    time.sleep(0.1)
    #print(n)
  
if __name__ == '__main__':

    l = []
    print('主线程 的 进程号:',getpid()) #主线程与子线程在同一个进程中
    for i in range(10):#开启10个线程
        t =Thread(target=fun,args = (i,))#创建线程,传递参数
        t.start()#启动线程
        l.append(t)#添加线程对象
    
    for j in l:
        j.join()#等待线程执行结束
    print(g)    #输出共享数据

#主线程 的 进程号: 5404
#0号子线程 的 进程号:5404
#1号子线程 的 进程号:5404
#2号子线程 的 进程号:5404
#3号子线程 的 进程号:5404
#4号子线程 的 进程号:5404
#5号子线程 的 进程号:5404
#6号子线程 的 进程号:5404
#7号子线程 的 进程号:5404
#8号子线程 的 进程号:5404
#9号子线程 的 进程号:5404
#90

全局解释器锁GIL

线程对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行
即对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在访问CPU
它会导致多线程的执行效率变低,但是可以避免不同的线程操作内部的共享数据
它是不同线程同时访问时Python解释器对于数据的保护机制
Python专家推荐我们使用多进程代替多线程,而不是去试图隐藏Python线程实现的不足

为什么有了gil锁还会出现数据安全问题?

因为CPU对线程的轮转切换导致有的线程获取到了gil锁后获取到公共资源,修改后还未返回资源
就被释放了gil锁,其他继续去获取gil去获取资源并修改,其他线程修改后的资源返回覆盖了之前线程返回的资源
所以共享的资源也要套上一层锁

线程中的锁

from threading import Lock 普通锁
from threading import RLock	可重入锁
锁的创建
lock=threading.Lock()
rlock=threading.RLock()

两者有什么区别?
同一个线程中多次lock.acquire()就会阻塞当前线程,可能造成死锁现象

rlock是递归锁(可重入锁),为了解决死锁问题
同一个线程中多次RLock的acquire()不会形成阻塞,跳过继续向下执行
如果使用RLock,那么acquire和release必须成对出现
同一个线程有多少acquire()就要有多少release()
否则其它线程无法获取acquire()
不同线程中,一个线程获取到rlock的锁,其它线程就无法获取当前rlock对象的锁
不同线程中,一个线程获取到lock的锁,其它线程还可以获取不同lock对象的钥匙
import time
from threading import Thread,RLock #递归锁 为了解决死锁问题
fork_lock = noodle_lock  = RLock()   
def eat1(name):
    noodle_lock.acquire()   #获取锁
    print('%s——1'%name)
    fork_lock.acquire()#再次获取锁,不会阻塞
    print('%s——2'%name)
    print('%s——3'%name)
    fork_lock.release()#释放锁
    noodle_lock.release()#释放锁

def eat2(name):
    fork_lock.acquire()#获取锁
    print('%s——1'%name)
    time.sleep(1)
    noodle_lock.acquire()#获取锁
    print('%s——2'%name)
    print('%s——3'%name)
    noodle_lock.release()#释放锁
    fork_lock.release()#释放锁

Thread(target=eat1,args=('张三',)).start()
Thread(target=eat2,args=('李四',)).start()
Thread(target=eat1,args=('王五',)).start()
#张三——1
#张三——2
#张三——3
#李四——1
#李四——2
#李四——3
#王五——1
#王五——2
#王五——3
#使用同一个RLock对象作为锁对象时,获取的锁的线程没有完全释放锁的时候
#其它线程就无法获取同一个对象的锁

守护线程

守护进程随着主进程代码的执行结束而结束
守护线程会在主线程结束之后等待其他非守护子线程的结束才结束
主线程的结束意味着进程的结束,进程整体的资源都会被回收,而进程必须保证非守护线程都运行完毕才会结束
主进程在其代码运行完毕后就会结束(守护进程会被回收),而主进程真正被回收是在非守护子进程都结束并被回收资源,主进程才会被回收资源
import time
from threading import Thread
def func1():
    while True:#无限循环输出的守护线程
        print('*'*10)
        time.sleep(1)
        
def func2():#自动结束的普通线程
    print('in func2')
    time.sleep(5)

t = Thread(target=func1,)
t.daemon = True#声明一个守护线程
t.start()#启动守护线程
t2 = Thread(target=func2,)#声明一个普通的非守护线程
t2.start()#启动普通线程
print('主线程')#主线程执行完毕
#**********
#in func2
#主线程
#**********
#**********
#**********
#**********

#启动一个守护线程与一个普通线程
#普通线程执行后,打印in func2然后等待5秒
#这5秒中主线程打印输出后结束,而守护线程继续打印数据,等待普通线程结束
#5秒后普通线程结束
#守护线程随之结束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值