哪有什么岁月静好,不过是有人替你负重前行!

pathon之多线程详解

一、线程理论

1、什么是线程
线程指的是一条流水线的工作过程

进程根本就不是一个执行单位,进程其实是一个资源单位--------将资源集合到一起:
一个进程内自带一个线程,线程才是CPU上的执行单位

2、进程VS线程
1、同一进程内的线程们共享该进程内资源,不同进程内的线程资源肯定是隔离的
2、创建线程的开销比创建进程要小的多(大概是100倍)

把操作系统比做一座工厂进行比对:
工厂=====》车间=====》流水线
操作系统====》进程=====》线程


开启一个进程,就是申请了一个内存空间,将产生的数据丢掉里面,而代码的运行就是线程
多个进程内存空间彼此是隔离的,而同一个进程下的多个线程,共享该进程内的数据,分散于不同进程之间的线程数据是隔离的

3、创建进程的开销远大于开启线程的开销:
造进程要向操作系统发请求,由操作系统帮你申请一个内存空间,
在一个进程再开启一个请求,不需要在申请内存空间,只需要告诉操作系统只需要执行代码即可

创建进程的开销要远大于线程
形象比喻:
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

进程之间是竞争关系,线程之间是协作关系
形象比喻:
车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)

二、开启线程的两种方式

方式一、

直接调用线程类,指定开启的子进程函数

#开启线程的方式一、
# 常用方式:
from multiprocessing import Process
from threading import Thread
import time

def task(name):
    print('%s is running' %name)       #最先被打印出来
    time.sleep(3)
    print('%s is done' % name)        #最后被打印出来

if __name__ == '__main__':      #线程可以不用__main__,但是开启进程必须用,因为开启进程为从新在导入模块
    # t=Thread(target=task,args=('egon',))
    t=Process(target=task,args=('egon',))
    t.start()                  #开启线程的速速很快,几乎向操作系统发起请求就被开启(不需要申请内存空间);而开启进程要向操作系统发起请求,申请内存空间,而这个时间已经够运行到下一行代码的了
    print('主线程')

#开启线程打印结果:
'''
egon is running
主线程
egon is done
'''

#开启进程打印结果:
'''
主线程
egon is running
egon is done
'''

 

方式二、

自定义一个线程类,并继承Thread类,然后调用自定义类

#自定义一个类、但是还要用被人内置的方法名
from multiprocessing import Process
from threading import Thread
import time

class MyThread(Thread):

    def run(self):
        print('%s is running' %self.name)
        time.sleep(3)

if __name__ == '__main__':
    t=MyThread()
    t.start()
    print('主线程')

 

三、进程与线程对比

#2、线程创建开销小-----因为其不需要申请内存空间
# 线程的开启速度更快
from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello                #子进程内的打印先被打印出来,因为子进程开启不需要申请内存空间,所以开启速度非常快
    主线程/主进程
    '''

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程        #由于子进程的开启需要申请内存空间,需要时间,而代码的运行速度是很快的,所以'主线程/主进程' 会先与子进程的代码被打印出来
    hello
    '''
线程的开销小,开启速度更快
from threading import Thread
import time,os
import time
x=100
def task():
    global x
    x=0           #子线程内的修改,可以站在主线程的角度去看,确实变量的值也被修改了

if __name__ == '__main__':
    t=Thread(target=task,)
    t.start()
    # time.sleep(3)    #模拟开启线程和线程进行修改数据的时间
    t.join()           #主线程等着子线程运行完毕
    print('主线程',x)
同一进程内的多个线程共享该进程内的资源(数据)
'''疑问线程有没有pid?
线程有没有杀死的需求'''
from threading import Thread
from multiprocessing import Process
import time,os
'''同一个进程内,主线程和子线程的PID是一样的'''
def task():
    print('%s is running' %os.getpid())
    time.sleep(3)

if __name__ == '__main__':
    t=Thread(target=task,)
    t.start()
    print('主线程',os.getpid())

    '''开启多个子进程每个子进程都有不同的PID'''
    p1=Process(target=task)
    p2=Process(target=task)
    p1.start()
    p2.start()
    print('主线程、主进程',os.getpid())
观察PID

 

四、线程对象的其他方法

1、Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。

2、threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
3、线程对象其他方法的应用
from threading import Thread,current_thread,active_count,enumerate
import time,os

def task():
    print('%s is running' %current_thread().name)    #在当前线程查看自己的线程名
    time.sleep(3)
    print('%s is done' % current_thread().getName()) #在当前线程查看自己的线程名current_thread().name等价于current_thread().getName()
if __name__ == '__main__':
    t1=Thread(target=task,name='第一个线程')
    # t2=Thread(target=task,)
    # t3=Thread(target=task,)
    t1.start()
    # t2.start()
    # t3.start()

    # print(t1.is_alive())      #判断线程是否存活
    print(active_count())       #活跃的线程个数
    print(enumerate())          #把当前活跃的线程对象放到列表中,可以通过点来使用线程的属性
    current_thread().setName('修改后的主线程名')
    print('主线程',current_thread().name)       #查看主线程的名字,也可以修改主线程的名字

 

五、守护线程

守护线程会在什么时候死?
守护线程会等到该进程内所有的非守护线程都结束才跟着结束
守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)
强调:
守护进程是父进程结束,守护的进程就随之结束
from threading import Thread,current_thread
import time

def task():
    print('%s is running' %current_thread().name)
    time.sleep(3)
    print('%s is done' % current_thread().name)    #守护的线程已经结束,所以看不到

if __name__ == '__main__':
    t1=Thread(target=task,name='第一个线程')
    t1.daemon = True
    t1.start()     #由于开的是线程的速度太开了,所以 print('%s is running' %current_thread().name)会被先打印出来
    print('主线程')

'''
打印结果:
第一个线程 is running
主线程

'''

 守护进程要等所有的非守护进程结束才会结束,而不是主线程结束守护进程就结束
from threading import Thread
import time
def foo():
    print(123)
    time.sleep(5)
    print("end123")        #不可能看到,原因是所有的非守护线程都已经运行完毕,自己也就随之结束,只有当睡的时间比非守护线程短才能看到

def bar():
    print(456)
    time.sleep(3)
    print("end456")

if __name__ == '__main__':

    t1=Thread(target=foo)
    t2=Thread(target=bar)

    t1.daemon=True
    t1.start()
    t2.start()
    print("main-------")

    '''
    123
    456
    main-------
    end456
    '''

六、线程互斥锁

同一时间只有一个人能拿到锁

from threading import Thread,Lock
import time

mutex=Lock()
x=100

def task():
    global x
    mutex.acquire()     #大家都声明了全局变量,加锁让他们等,同一时间只有一个人拿到
    temp=x              #保证100个线程都拿到了x,即都拿到了初始值
    time.sleep(0.1)     #都睡了0.1秒,这个时间已经都后面99个线程起来,那么他们谁先谁后抢到锁就不一定了
    x=temp-1            #正常已改改为0
    mutex.release()


if __name__ == '__main__':
    start=time.time()
    t_l=[]
    for i in range(100):         #用for循环是有先后顺序,真实可能是同时起来的
        t=Thread(target=task)
        t_l.append(t)
        t.start()         #速度太快了,几乎是start同时
    for t in t_l:
        t.join()

    print('',x)
    print(time.time()-start)

 

七、死锁现象及解决方法递归锁

死锁现象:

死锁现象是由于锁套锁导致的,两个线程互相拿到对方的锁就都被锁死了
'''死锁现象'''
from threading import Thread,Lock,RLock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print('%s 拿到了A锁' %self.name)

        mutexB.acquire()
        print('%s 拿到了B锁' %self.name)
        mutexB.release()

        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print('%s 拿到了B锁' %self.name)
        time.sleep(0.1)

        mutexA.acquire()
        print('%s 拿到了A锁' %self.name)
        mutexA.release()

        mutexB.release()


if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()


'''
Thread-1 拿到了A锁      #线程1首先抢到了A锁,还没有释放,其他人只有等线程1把A锁释放,才能抢到A锁
Thread-1 拿到了B锁      #线程1又抢到了B锁,然后释放掉了B锁和A锁,那么其他人此时就可以争抢A锁,
Thread-1 拿到了B锁      #线程1紧接着又拿到了B锁,睡了一会准备去抢A锁,但是此时A锁已经被被人抢走,所以就会在原地阻塞住
Thread-2 拿到了A锁      #线程1释放的A锁被线程2抢到,然后线程2又想去抢A锁,但是此时B锁已经被线程1抢走,所以线程1就被阻塞住
#这就导致线程1和线程2互相锁死的现象,我们采用递归锁来进行解决死锁现象
死锁现象

解决死锁现象

from threading import Thread,Lock,RLock
import time

mutexA=Lock()
mutexB=Lock()

mutexA=mutexB=RLock()       #RLock递归锁来解决死锁现象,递归锁的特点是可以连续的acquire

class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print('%s 拿到了A锁' %self.name)

        mutexB.acquire()
        print('%s 拿到了B锁' %self.name)
        mutexB.release()

        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print('%s 拿到了B锁' %self.name)
        time.sleep(0.1)

        mutexA.acquire()
        print('%s 拿到了A锁' %self.name)
        mutexA.release()

        mutexB.release()


if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

    t1=MyThread()
    t1.start()

    t2=MyThread()
    t2.start()

    t3=MyThread()
    t3.start()
    print('')
递归锁解决死锁现象

 

八、信号量

# from multiprocessing import Semaphore
# 信号量也是一种锁
from threading import Thread,Semaphore,current_thread
import time,random

# 同一时间并发执行的任务数
# 而互斥锁同一时间只有一个正在运行
sm=Semaphore(5)     #得到一个信号量的对象

def go_wc():
    sm.acquire()
    print('%s 上厕所ing' %current_thread().getName())
    time.sleep(random.randint(1,3))
    sm.release()

if __name__ == '__main__':
    for i in range(23):
        t=Thread(target=go_wc)
        t.start()

 

posted @ 2018-07-13 17:35  迎风而来  阅读(500)  评论(0编辑  收藏  举报
/*吸附球*/