线程

2018-04-24  本文已影响0人  furuiyang

什么是线程?

1.一个程序运行起来至少有一个进程,一个进程至少有一个线程
2.处理器cpu分配给线程,即cpu真正运行的是线程
3.分配cpu资源给线程时,是通过时间片轮训方式进行的
4.进程是操作系统分配程序执行资源的单位,而线程是进程的一个实体,
 是CPU调度和分配的单位。

开启多线程

python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用,通过threading模块可以创建线程。

import threading
import time


def download_music():
    """模拟下载歌曲,需要5秒钟下载完成"""
    for i in range(5):
        time.sleep(1)  # 休眠1秒
        print("---正在下载歌曲%d---" % i)


def play_music():
    """模拟播放歌曲,需要5秒钟下载完成"""
    for i in range(5):
        time.sleep(1)  # 休眠1秒
        print("---正在播放歌曲%d---" % i)


def main():
    # 创建线程对象t1
    # target: 指向新开启的线程要执行的代码
    t1 = threading.Thread(target=download_music)
    t2 = threading.Thread(target=play_music)

    t1.start()  # 启动线程,既然线程开始执行
    t2.start()


if __name__ == '__main__':
    main()

运行结果:

多线程听歌程序运行结果.png

说明

  1. 可以明显看出使用了多线程并发的操作,花费时间要短很多;
  2. 当调用start()时,才会真正的执行线程,执行线程中的代码。

线程何时开始和结束

1.子线程何时开启,何时运行
   当调用thread.start()时 开启线程,再运行线程的代码
2.子线程何时结束
   子线程把target指向的函数中的语句执行完毕后,立即结束当前子线程
3.查看当前线程数量
   通过threading.enumerate()可枚举当前运行的所有线程
4.主线程何时结束
   所有子线程执行完毕后,主线程才结束
import threading
import time

def test1():
    for i in range(5):
        time.sleep(1)
        print('---子线程1---%d' %i)
        print('子线程1中查看线程情况',threading.enumerate())

def test2():
    for i in range(10):
        time.sleep(1)
        print('---子线程2---%d' %i)
        print('子线程2中查看线程情况',threading.enumerate())

def main():
    print('创建线程之前的线程情况',threading.enumerate())
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    time.sleep(1)
    print('创建线程之后的线程执行情况',threading.enumerate())
    t1.start()
    t2.start()
    time.sleep(1)
    print('调用了thread.start()之后的线程情况',threading.enumerate())
    t2.join()  # 当t2线程执行完毕之后 在执行后续的代码
    print('列表目前的线程',threading.enumerate())

if __name__ == '__main__':
    main()

运行结果略,可以自行运行显示。

线程执行过程中的注意点

线程执行代码的封装

通过上一小节,能够看出,通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法。

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            time.sleep(1)
            msg = "I'm "+ self.name + '@' + str(i)
            print(msg)

if __name__ == '__main__':
    t1 = MyThread()
    t1.start()

执行结果:

线程执行代码的封装.png

说明:

python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。

线程的执行顺序

# coding:utf-8
import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm " + self.name + '@' + str(i)
            print(msg)

def test():
    for i in range(5):
        t = MyThread()
        t.start()

if __name__ == '__main__':
    test()

执行结果:

线程的执行顺序.png

说明:

从代码和执行结果我们可以看出,多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。上面的代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。

总结

1 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
2 当线程的run()方法结束时该线程完成。
3 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

多线程共享全局变量

总结

在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)

示例代码

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()

运行结果

11.png

列表当做实参传递到线程中

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()
22.png

多线程-共享全局变量的问题

多线程开发可能会遇到的问题

假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
但是由于是多线程同时操作,有可能出现下面情况:
1.在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
2.然后t2对得到的值进行加1并赋给g_num,使得g_num=1
3.然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
4.这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1

进行一个测试代码:

import threading
import time

g_num = 0

def work1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print('---in work1,g_num is %d---' %g_num)

def work2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print('---in work2,g_num is %d---' %g_num)

print('---线程创建之前g_num is %d---'%g_num)

t1 = threading.Thread(target=work1,args=(100,))
t1.start()

t2 = threading.Thread(target=work2,args=(100,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print('2个线程对同一个全局变量操作之后的最终结果是%s' %g_num)

运行结果

33.png

加大num

...
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
...
44.jpg

再次总结

如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确。

上一篇下一篇

猜你喜欢

热点阅读