python并发编程之多线程

什么是线程

在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程

线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程
  
车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线
流水线的工作需要电源,电源就相当于cpu

所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

线程的创建开销小

创建进程的开销要远大于线程?

如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)

一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)

创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)

而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

进程之间是竞争关系,线程之间是协作关系?

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

为何要用多线程

多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

  1. 多线程共享一个进程的地址空间
  2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
  3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
  4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

Python 实现多线程编程需要借助于 threading 模块。
threading 模块中最核心的内容是 Thread 这个类。

import threading

我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。

值得注意的是,程序运行时默认就是在主线程上
创建 Thread 对象有 2 种手段。

  1. 直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
  2. 编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

1. 直接创建 Thread 对象。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
#args 是固定参数,为元组类型,kwargs 是可变参数。

Thread 的构造方法中,最重要的参数是 target,所以我们需要将一个 callable 对象赋值给它,线程才能正常运行。

如果要让一个 Thread 对象启动,调用它的 start() 方法就好了。

import threading
import  time

def test():
    for i in range(5):
        print('test',i)
        time.sleep(1)

thread=threading.Thread(target=test)
thread.start()

for i in range(5):
    print('main',i)
    time.sleep(1)
#打印
test  0
main  0
main  1
test  1
main  2
test  2
main  3
test  3
main  4
test  4

Thread 的名字

每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。

如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。

import threading
import  time

def test():
    for i in range(5):
        print(threading.current_thread().name+'test',i)
        time.sleep(1)

thread=threading.Thread(target=test)
thread.start()

for i in range(5):
    print(threading.current_thread().name + 'main', i)
    time.sleep(1)
##打印
Thread-1 test  0
MainThread main  0
Thread-1 test  1
MainThread main  1
Thread-1 test  2
MainThread main  2
Thread-1 test  3
MainThread main  3
Thread-1 test  4
MainThread main  4

如果我们在 Thread 对象创建时,构造方法里面赋值。
thread = threading.Thread(target=test,name='TestThread')
打印

TestThread test  0
MainThread main  0
MainThread main  1
TestThread test  1
MainThread main  2
TestThread test  2
MainThread main  3
TestThread test  3
MainThread main  4
TestThread test  4

Thread 的生命周期

Thread 的生命周期

  • 创建对象时,代表 Thread 内部被初始化。
  • 调用 start() 方法后,thread 会开始运行。
  • thread 代码正常运行结束或者是遇到异常,线程会终止。

可以通过 Thread 的 is_alive() 方法查询线程是否还在运行。

join() 提供线程阻塞手段

上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?
调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(0.5)


thread = threading.Thread(target=test,name='TestThread')
thread.start()
thread.join()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)
##打印
# TestThread test  0
# TestThread test  1
# TestThread test  2
# TestThread test  3
# TestThread test  4
# MainThread main  0
# TestThread is alive  False
# MainThread main  1
# TestThread is alive  False
# MainThread main  2
# TestThread is alive  False
# MainThread main  3
# TestThread is alive  False
# MainThread main  4
# TestThread is alive  False

默认的情况是,join() 会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间就好了。
def join(self, timeout=None):
timeout 是一个浮点参数,单位是秒。

如果我们更改上面的代码。
thread.join(1.0)

TestThread test  0
TestThread test  1
MainThread main  0
TestThread is alive  True
TestThread test  2
TestThread test  3
MainThread main  1
TestThread is alive  True
TestThread test  4
MainThread main  2
TestThread is alive  False
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

Thread 中的 daemon 属性

Thread 的构造方法中有一个 daemon 参数。默认是 None。

daemon 起什么作用呢?

# 主线程执行代码的时长比 TestThread 要短。
import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(2)


thread = threading.Thread(target=test,name='TestThread')
# thread = threading.Thread(target=test,name='TestThread',daemon=True)
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)
# 打印
TestThread test  0
# MainThread main  0
# TestThread is alive  True
# MainThread main  1
# TestThread is alive  True
# TestThread test  1
# MainThread main  2
# TestThread is alive  True
# MainThread main  3
# TestThread is alive  True
# TestThread test  2
# MainThread main  4
# TestThread is alive  True
# TestThread test  3
# TestThread test  4

MainThread 没有代码运行的时候,TestThread 还在运行。

这是因为 MainThread 在等待其他线程的结束。

TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?

其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。

当然也可以在子线程的构造器中传递 daemon 的值为 True。

thread = threading.Thread(target=test,name='TestThread',daemon=True)
# thread.setDaemon(True)


#打印
# TestThread test  0
# MainThread main  0
# TestThread is alive  True
# MainThread main  1
# TestThread is alive  True
# TestThread test  1
# MainThread main  2
# TestThread is alive  True
# MainThread main  3
# TestThread is alive  True
# TestThread test  2
# MainThread main  4
# TestThread is alive  True

2.自定义类继承 Thread

自定义一个 Thread 的子类,然后复写它的 run() 方法。

import threading
import time


# 自定义线程类,继承自Thread类并重写其run方法
class TestThread(threading.Thread):
    def __init__(self,name=None):
    	# 继承父类__init__方法
        threading.Thread.__init__(self,name=name)
	
	# 重写run方法   定义每个线程都要运行的函数
    def run(self):
        for i in range(5):
            print(threading.current_thread().name+'test',i)
            time.sleep(1)

thread=TestThread(name='TestThread')
thread.start()

for i in range(5):
    print(threading.current_thread().name+'mian',i)
    print(thread.name+'is alive',thread.isAlive())
    time.sleep(1)
# 打印
# TestThread test  0
# MainThread main  0
# TestThread is alive  True
# TestThread test  1
# MainThread main  1
# TestThread is alive  True
# TestThread test  2
# MainThread main  2
# TestThread is alive  True
# MainThread main  3
# TestThread is alive  True
# TestThread test  3
# MainThread main  4
# TestThread test  4
# TestThread is alive  True

为了让线程代码更好的封装。可以使用threading模块下的Thread类,继承自这个类,然后实现run方法,线程就会自动运行run方法中的代码。

import threading
import time

class CodingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('%s正在写代码' % threading.current_thread())
            time.sleep(1)

class DrawingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('%s正在画图' % threading.current_thread())
            time.sleep(1)

def multi_thread():
    t1 = CodingThread()
    t2 = DrawingThread()

    t1.start()
    t2.start()

if __name__ == '__main__':
    multi_thread()

线程相关的其他方法

Thread实例对象的方法

  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

threading模块提供的一些方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。



import threading #线程
import time

def Hi(num):
    print("hello %d"%num)
    time.sleep(3)


if __name__ == '__main__':

    t1=threading.Thread(target=Hi,args=(10,))#创建了一个线程对象t1
    t1.start()

    t2 = threading.Thread(target=Hi, args=(9,))  # 创建了一个线程对象t2
    t2.start()

    print("ending..........")
#打印
hello 10
hello 9
ending..........

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值