注:本文的编程语言使用的是Python。
多任务的概念
多任务处理是指用户可以在同一时间内运行多个应用程序,
每个应用程序被称作一个任务.Linux、windows就是支持多任务的操作系统。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,
操作系统也会自动把很多任务轮流调度到每个核心上执行。
并发:指的是任务数多雨cpu核数,通过操作系统的各种任务调度算法,实现用多个任务
“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的。
线程
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,
是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,
但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用。
多线程
#coding=utf-8
import threading
import time
def test():
print("你好,")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=test)
t.start() #启动线程,即让线程开始执行
说明:
使用了多线程并发的操作,花费时间要短很多
当调用start()时,才会真正的创建线程,并且开始执行
每个线程都有一个唯一标示符,来区分线程中的主次关系
主线程:mainThread,Main函数或者程序主入口,都可以称为主线程
子线程:Thread-x 使用 threading.Thread() 创建出来的都是子线程
线程数量:主线程数 + 子线程数
主线程会等待所有的子线程结束后才结束
#导入sleep模块,ctime模块,线程threading模块
from time import sleep,ctime
import threading
#设置多线程的运行程序函数
def say():
for i in range(3):
print("你好啊,我的名字是")
sleep(1)
def say2():
for i in range(3):
print("年华终归属时光")
sleep(1)
#多线程的带参数传递
def sing(a,b,c):
print("----sing----a = %d, b = %d,c = %d" % (a, b, c))
for i in range(3):
print("一杯酒两角眼...")
sleep(1)
if __name__ == '__main__':
#将say,say2封装为线程并让其启动
print("---开始计时----%s"%ctime())
a1 = threading.Thread(target=say)
a2 = threading.Thread(target=say2)
#使用args传递参数
a3 = threading.Thread(target=sing,args=(10,100,100))
#使用kwargs传递参数
a4 = threading.Thread(target=sing, kwargs={"a": 10, "b": 100, "c": 100} )
# 设置为守护线程(如果主线程结束了,也随之结束)
a1.setDaemon(True)
a1.start()
a2.start()
a3.start()
a4.start()
print('---计时结束---%s'%ctime())
#统计当前正在运行的线程数量
while True:
length=len(threading.enumerate())
print("当前运行的线程数为%d"%length)
if length <=1:
break
sleep(1)
多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),
到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。
上面的代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
总结
每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
当线程的run()方法结束时该线程完成。
无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
守护线程
守护线程:如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,
设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。
设置守护线程:
# 设置为守护线程(如果主线程结束了,也随之结束)
线程.setDaemon(True)
线程执行代码的封装(自定义线程类)
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要
继承threading.Thread
重写run方法
python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。
而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,
交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
super(MyThread, self).__init__() 调用父类的构造方法
**** super用法 ****
super(type【类】,object-or-type 【类,一般是 self】)
'''python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。
而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,
当该线程获得执行的机会时,就会调用run方法执行线程。'''
#conding=utf-8
import time
import threading
class MyThread(threading.Thread):
def __init__(self,name,city):
super(MyThread,self).__init__()
self.name = name
self.city = city
def run(self):
for i in range(3):
time.sleep(1)
msg = "i'm "+self.name+"来自"+self.city+str(i)
print(msg)
t = MyThread("年华终归属时光","北京")
t.start()
多线程共享全局变量
from threading import Thread
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
print("----in work1, g_num is %d---"%g_num)
def work2():
global g_num
print("----in work2, g_num is %d---"%g_num)
print("---线程创建之前g_num is %d---"%g_num)
t1 = Thread(target=work1)
t1.start()
#延时一会,保证t1线程中的事情做完
time.sleep(1)
t2 = Thread(target=work2)
t2.start()
列表当做实参传入线程中
from threading import Thread
import time
def work1(nums):
nums.append(44)
print("----in work1---",nums)
def work2(nums):
#延时一会,保证t1线程中的事情做完
time.sleep(1)
print("----in work2---",nums)
g_nums = [11,22,33]
t1 = Thread(target=work1, args=(g_nums,))
t1.start()
t2 = Thread(target=work2, args=(g_nums,))
t2.start()
多线程共享变量遇到的问题:
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
但是由于是多线程同时操作,有可能出现下面情况:
在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
然后t2对得到的值进行加1并赋给g_num,使得g_num=1
然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
线程的join()方法的使用
假如有A、B两个线程同时执行,如果想要让A线程执行完成后,再执行线程B,那么A线程可以调用join()方法来完成,
Join方法:如果一个线程在执行过程中要调用另外一个线程,并且等到其完成以后才能接着执行,
线程名.join()