认识python中的线程

一、线程和进程对比

  • 定义的不同

进程是系统进行资源分配和调度的一个独立单位。

     线程是CPU调度和分派的基本单位,它可与同属一个进程的其他线程共享进程所拥有的全部资源.

  • 区别

一个程序至少有一个进程,一个进程至少有一个线程;

线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高;

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率;

线程不能够独立执行,必须依存在进程中

  • 优缺点

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

 

二、线程的创建

  threading模块是python封装的多线程模块,创建线程时,既可以通过threading.Thread类,也可以定义一个新的子类,然后继承于threading,Thread类。

  1.Thread类创建

  创建一个Thread实例,只需要传入一个执行函数和函数的参数,用start()方法启动

  示例:

#coding=utf-8
from threading import Thread,enumerate
from time import sleep,ctime

def sing():
    for i in range(3):
        print("正在唱歌...%d"%i)
        sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d"%i)
        sleep(1)

if __name__ == '__main__':
    print('---开始---:%s'%ctime())
    t1 = Thread(target=sing)
    t2 = Thread(target=dance)
    t1.start()
    t2.start()
    print("当前线程的数量:%d"%len(enumerate()))#查看线程数量

  2.Thread子类创建

  Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start()方法,可以启动该线程

  示例:

#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) #name属性中保存的是当前线程的名字
            print(msg)

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

 

三、线程同步机制--互斥锁

  一个进程内的所有线程共享全局变量,但是线程对全局变量随意遂改可能造成数据破坏,使得线程运行的结果不可预期(即线程非安全)。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

  互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

  互斥锁对象threading.Lock()的方法:

  • acquire([blocking[,timeout]])----锁定

    • blocking参数若为True,则当前线程会堵塞,直到获取到这个锁为止,若为False,则当前线程不会堵塞。默认为True
    • timeout参数设置超时时间,超过这一时间(单位:秒)后仍未获取到锁,则线程不会堵塞
    • 如果能够锁定成功,则返回True;如果不能锁定,blocking参数为False或者timeout设置了超时时间,则返回False
  • release-----释放锁

  示例:

from threading import Thread, Lock
import time

g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        #True表示堵塞 即如果这个锁在上锁之前已经被上锁了,那么这个线程会在这里一直等待到解锁为止 
        #False表示非堵塞,即不管本次调用能够成功上锁,都不会卡在这,而是继续执行下面的代码
        mutexFlag = mutex.acquire(True) 
        if mutexFlag:
            g_num += 1
            mutex.release()

    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range(1000000):
        mutexFlag = mutex.acquire(True) #True表示堵塞
        if mutexFlag:
            g_num += 1
            mutex.release()

    print("---test2---g_num=%d"%g_num)

#创建一个互斥锁
#这个所默认是未上锁的状态
mutex = Lock()

p1 = Thread(target=test1)
p1.start()


p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)

  上锁的利弊:

    好处:

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

 

四、死锁 

  在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应。

  下面看一个死锁的例子:

#coding=utf-8
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name+'----do1---up----')
            time.sleep(1)

            if mutexB.acquire():
                print(self.name+'----do1---down----')
                mutexB.release()
            mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name+'----do2---up----')
            time.sleep(1)
            if mutexA.acquire():
                print(self.name+'----do2---down----')
                mutexA.release()
            mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

上面例子中,t1和t2线程分别对mutexA和mutexB进行了上锁,然后互相等待对方释放锁,再进行上锁,最终形成了死锁。

  如何避免死锁?

  • 程序设计时要尽量避免(银行家算法)
  • 添加超时时间等

 

五、ThreadLocal

  在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

  示例:

import threading

# 创建全局ThreadLocal对象:
local_school = threading.local()

def process_student():
    # 获取当前线程关联的student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))

def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()

t1 = threading.Thread(target= process_thread, args=('dongGe',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

  ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

posted @ 2021-03-23 00:07  eliwang  阅读(309)  评论(0)    收藏  举报