Python----线程(Threading)从入门到应用_1

一、what is 线程 ?

线程是操作系统能够进行运算调度的最小单位。线程被包含在进程中,是进程中实际处理单位。一条线程就是一堆指令集合。一条线程是指进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

二、what is 进程 ?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

三、线程 VS 进程

(1)进程(process)是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
(2)进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。
(3)线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。
(4)线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
(5)处理IO密集型任务或函数用线程;多线程采用的是分时复用技术,即不存在真正的多线程,cpu做的事是快速地切换线程,以达到类似同步运行的目的,因为高密集运算方面多线程是没有用的,但是对于存在延迟的情况(延迟IO,网络等)多线程可以大大减少等待时间,避免不必要的浪费。
(6)处理计算密集型任务或函数用进程,每个cpu可以执行一个进程,不需要进程间切换。
(7)详见第七条/八条分析…


四、创建线程

Thread 是threading模块中最重要的类之一,可以使用它来创建线程。有两种方式来创建线程:

sleep的时候是不会占用cpu的,在sleep的时候操作系统会把线程暂时挂起。

1.普通创建

通过threading.Thread()创建线程,其中target接收的是要执行的函数名字,args接收传入函数的参数,以元组的形式表示。

import threading
import time


def run(n):
    print("task", n)
    time.sleep(1)
    print('2s')
    time.sleep(1)
    print('1s')
    time.sleep(1)
    print('0s')
    time.sleep(1)


t1 = threading.Thread(target=run, args=("t1",))
t2 = threading.Thread(target=run, args=("t2",))
t1.start()
t2.start()

结果:

task t1
task t2
2s
2s
1s
1s
0s
0s
  • 以上结果每2行先打印,说明2个线程都同时执行了,使用不同的CPU
2.继承Thread,重构run方法
  • python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。
  • 而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数必须要写
        self.n = n

    def run(self):
        print("task", self.n)
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
        time.sleep(1)
        print('0s')
        time.sleep(1)


if __name__ == "__main__":
    t1 = MyThread("t1")
    t2 = MyThread("t2")

    t1.start()
    t2.start()

结果:

task t1
task t2
2s
2s
1s
1s
0s
0s
  • 以上结果每2行先打印,说明2个线程都同时执行了,使用不同的CPU
  • 运行的结果和打印效果与普通创建一样

五、主线程&子线程

  • 当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元.
  • 当设置多线程时,主线程会创建多个子线程。
  • 在python中,默认情况下(其实就是setDaemon(False) ),主线程执行完自己的任务以后,就退出了。但此时子线程会继续执行自己的任务,直到自己的任务结束。见案例一
  • 如果设置子线程为守护线程(setDaemon(True) ),则主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。见案例二
1.默认情况下的主线程&子线程----主程序需要等待子线程
import threading
import time

def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.start()

    print('主线程结束!' , threading.current_thread().name)
    print('一共用时:', time.time()-start_time)


这是主线程: MainThread    			# 首先打印前面三行
主线程结束! MainThread
一共用时: 0.001096963882446289

当前线程的名字是:  Thread-1 			# 等待2s左右,再执行后面5行
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-4
当前线程的名字是:  Thread-3
当前线程的名字是:  Thread-5
						
Process finished with exit code 0  # 等待大约2s,程序执行完毕
  • 如上案例的分析,说明只要运行程序,都会有个主线程
  • 创建的子线程运行与主线程,默认情况下互不影响,主线程执行完了,子线程还可以继续执行。
2.设置子线程为守护线程setDaemon(True)—主程序不需要等待子线程
import threading
import time


def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.setDaemon(True)   # 设置守护线程,未启动的线程才能设置守护线程
        t.start()

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time() - start_time)

结果:


这是主线程: MainThread       # 运行程序,直接执行了前三行(主线程)
主线程结束了! MainThread
一共用时: 0.0007190704345703125

Process finished with exit code 0 # 执行完,立即结束程序
  • 通过以上案例,发现子线程设置为守护线程之后,主线程执行完,程序就结束了。
    子线程无法完整得到执行。
3.设置阻塞状态join—主线程等待子线程一定时间再执行
  • 阻塞主进程和主线程,只要子线程设置了join方法,主线程就需要等待此子线程,适合依次执行多子线程

  • 多个子线程join的情况下,依次执行各子线程的join方法。前头一个结束了才能执行后面一个。这里的依次执行,是有计算内容的时候,如果是sleep状态,则相当于程序挂起,空置状态,需要区别对待

  • json里面timeout无参数,为无限等待,知道该线程结束,才开始执行下一个线程的join。

  • 参数timeout为线程的阻塞时间,如 timeout=2 ,就是主线程会等待这个线程2s ,2s之后就不管他了,线程被杀死,继续执行其他子线程或主线程

import threading
import time


def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.start()
        t.join()

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time() - start_time)

结果:

这是主线程: MainThread 				# 先执行这一行,主线程

当前线程的名字是:  Thread-1			# 每个线程依次执行,间隔2s
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-3
当前线程的名字是:  Thread-4
当前线程的名字是:  Thread-5

主线程结束了! MainThread			    # 最后执行主线程的打印
一共用时: 20.027244091033936							

  • 通过如上分析, 主程序、主线程都等待了被设置为join的子线程,等待子线程执行完之后,主程序、主线程才去执行。
  • 如果子线程不设置守护线程,只要设置join状态,最终主程序、主线程还是会等待子线程。

六、子线程里setDaemon(True) && join(timeout)同时使用

  • 当设置守护线程时setDaemon(True),意思是子线程是否完整运行对主线程不重要了,主线程结束,子线程也就不执行了。
  • 当设置子线程阻塞join(timeout)状态时,意思是主线程必须等待子线程,子线程运行时,主线程处于阻塞状态。
setDaemon()join()结果
FalseNone主线程与子线程执行互不影响,且都完整执行,直到所有线程执行完,程序结束
TrueNone主线程执行完,程序结束,子线程是否能完整执行不确定
Truejoin()1.先设置setDaemon和start()线程,其次join线程,则会依次执行线程,但是最终哪个线程先执行不确定,因为线程会强占资源;见如下案例一
2.先设置setDaemon,然后再start()线程和join每个子线程,则会依次执行每个字线程;见如下案例二
3.先start()线程并设置join,然后setDaemon线程会报错,因为join执行完毕了,线程处于活跃状态,不能setDaemon了;见如下案例三
Truejoin(n)1.先设置setDaemon和start()线程,其次join(n)线程,则会依次执行线程,但是最终哪个线程先执行不确定,因为线程会强占资源,但每个子线程可执行的时间由n确定;
2.先设置setDaemon,然后start()线程和join(n)每个子线程,则会依次执行每个字线程,每个子线程执行n秒
3.先start()线程并设置join(n),然后setDaemon线程会报错,因为join(n)执行完毕了,线程处于活跃状态,不能setDaemon了。
Falsejoin()子线程依次执行,然后主线程才会执行完毕,主进程才结束
Falsejoin(n)子线程依次执行,且子线程只执行N秒,然后主线程才会执行完毕,主进程才结束
1.先设置setDaemon和start()线程,其次join线程
import threading
import time


def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.setDaemon(True)
        t.start()

    for t in thread_list:
        t.join()

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time() - start_time)

这是主线程: MainThread

当前线程的名字是:  Thread-1		# 5个线程基本同时打印的,先执行哪个不确定
当前线程的名字是:  Thread-3		# 线程之间会强占资源,谁先抢到谁先执行
当前线程的名字是:  Thread-5
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-4

主线程结束了! MainThread
一共用时: 4.005864858627319

Process finished with exit code 0
2.先设置setDaemon,然后再start()线程和join每个子线程
import threading
import time


def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.setDaemon(True)

    for t in thread_list:
        t.start()
        t.join()

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time() - start_time)
这是主线程: MainThread

当前线程的名字是:  Thread-1	# 各个子线程依次执行
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-3
当前线程的名字是:  Thread-4
当前线程的名字是:  Thread-5

主线程结束了! MainThread		# 最后执行主程序
一共用时: 10.025158882141113

Process finished with exit code 0
3.先start()线程并设置join,然后setDaemon线程会报错
import threading
import time


def run():
    time.sleep(1)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(1)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.start()
        t.join()

    for t in thread_list:
        t.setDaemon(True)

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time() - start_time)
这是主线程: MainThread

当前线程的名字是:  Thread-1   # 各个子线程依次执行
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-3
当前线程的名字是:  Thread-4
当前线程的名字是:  Thread-5
							# 执行到setDaemon时候,报错,线程活跃状态不能设置守护线程
Traceback (most recent call last):
  File "/Users/test/threadStudy/threadJoin.py", line 26, in <module>
    t.setDaemon(True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1148, in setDaemon
    self.daemon = daemonic
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1141, in daemon
    raise RuntimeError("cannot set daemon status of active thread")
RuntimeError: cannot set daemon status of active thread

参考文章:
https://www.cnblogs.com/whatisfantasy/p/6440585.html
https://www.cnblogs.com/cnkai/p/7504980.html#commentform

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello-alien

您的鼓励,是我最大的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值