一、区别线程和进程

区分线程和进程:

         进程在操作系统中分配得到资源

         线程让操作系统运行代码的指针,是真真正正干活的人

        

        

【python系统编程2】多线程

         可以把程序的执行认为是线程的指针的运动,指针指到哪行代码,哪行代码就被执行了

 

 

二、入门案例

         在一个进程中开启多个线程,和开启进程的方法十分类似

 

#1.导入Thread
from threading import Thread   
import time

#2.定义线程执行的目标方法
def func():
   
for i in range(10):
       
print(i)
        time.sleep(
1)

if __name__ == '__main__':
   
#3.实例化一个线程对象,目标函数指向func
   
thread = Thread(target=func)
   
#4.开启thread线程
   
thread.start()
   
#5.在主线程也去执行func函数
   
func()

 

 

 

三、Thread类

3.1Thread的构造方法:

        

def __init__(self, group=None, target=None, name=None,

             args=(), kwargs=None, *, daemon=None):

        

group

一般不用

target

线程执行的目标函数

name

给线程起别名

args

以元组的方式给目标函数传递参数

kwargs

以字典的方式给目标函数传递参数

daemon

守护进程,一般不用

         代码演示:

        

from threading import Thread

  import time

  

  def func(param1,param2):

    while True:

        print('【新的进程】param1=%s,param2=%s' % (param1,param2))

        time.sleep(1)

  

  if __name__ == '__main__':

    thread = Thread(target=func,args=('value1','value2'),name='线程1')

    thread.start()

  

    while True:

        print(thread.name)

        time.sleep(1)

3.2Thread的常用方法和属性

is_alive

判断线程是否正在运行,返回True或者False

join(timeout=None)

堵塞主线程,指到子线程执行完或者超过最大的堵塞时间,才放行主线程,默认最大的堵塞时间无上限

getName()

得到线程的别名,和直接获取name属性的结果一样

setName(name)

设置线程的别名

代码演示:

from threading import Thread

  import time

  

  def func():

    for i in range(5):

        print(i)

        time.sleep(1)

  

  if __name__ == '__main__':

    thread = Thread(target=func)

    thread.start()

    print('开启了thread线程')

    print('thread线程是否is_alive',thread.is_alive())

    print('开始堵塞主线程')

    thread.join()

    thread.setName('子线程1')

    print('thread线程的name=',thread.getName())

    print('thread线程是否is_alive',thread.is_alive())

3.3把线程封装成类对象、

         步骤

                  1.定义子类继承Thread类

                  2.如果需要传递参数的话,重写__init__()方法

                  3.重写run方法,在thread.start()会直接运行类中的run方法

         代码演示:

        

from threading import Thread

  import time

  #定义一个类,继承Thread

  class My_Thread(Thread):

    #重写__init__()方法

    def __init__(self,param):

        #调用父类Thread的构造方法

        Thread.__init__(self)

        self.param = param

    #重写run方法,被start()调用

    def run(self):

        for i in range(5):

            print('子线程打印param=',self.param)

            time.sleep(1)

  

  if __name__ == '__main__':

    #实例化一个My_Thread类

    thread = My_Thread('value')

    #使用start方法,执行类中的run方法

    thread.start()

3.4多线程的生命周期

         通过Thread类创建出来的运行的线程,如果执行完毕,就会被销毁

         如果子线程还在运行,但主线程的代码已经执行完毕,主线程会等待子线程执行完,再停止主线程

         如果主线程被关闭,那么其余正在执行的子线程也会被关闭

 

 

 

四、线程中的通信

4.1使用全局变量通信

线程中的通信比进程通信方便的多,各个线程共享全局变量

from threading import Thread

  import time

param_s = []

  

  def func():

    for i in range(5):

        print(param_s)

        param_s.append(i)

        time.sleep(1)

  

  if __name__ == '__main__':

    thread1 = Thread(target=func)

    thread2 = Thread(target=func)

    thread1.start()

    thread2.start()

 

 

4.2多线程操作全局变量的问题

        

from threading import Thread

a = 0

  #定义目标函数,修改全局变量a

  def func():

    global a

    for i in range(100000):

        a += 1

  

  if __name__ == '__main__':

    #实例化线程1和线程2

    thread1 = Thread(target=func)

    thread2 = Thread(target=func)

    #开启线程1和线程2

    thread1.start()

    thread2.start()

    #堵塞主线程,等待线程1和线程2执行完

    thread1.join()

    thread2.join()

    #打印a的值

    print(a)

 

         理论上,最终打印的a应该等于20000,但实际上a的值却小于20000

         这是为什么呢????

        

         在多线程对同一个全局变量的操作密度非常的情况下,就可能发生两个线程同时更改这个全局变量,导致这两个的更改中有一方的操作丢失。

        

【python系统编程2】多线程

        

4.3同步和异步

         如何解决多线程一起操作共享资源的不安全性,先需要明白两个基本概念——同步和异步

         线程同步,并不是指两个线程一起同时执行,而是彼此相互依赖,就像双腿走路一样,右脚等着左脚落稳了,再往前迈,线程的执行有先后顺序,一个线程等待另一个线程执行到某一个阶段,再往下执行,主线程只有一个

        

【python系统编程2】多线程

         而异步:

         多个任务的执行没有先后顺序,自己执行执行自己的

 

4.4线程锁

        

【python系统编程2】多线程

threading模块提供了一个Lock类,来实现线程锁

from threading import Lock

常用命令

lock = Lock()

实例化一个线程锁

lock.acqure(block,timout)

如果lock没有上锁,那么就把lock锁起来,往下执行,如果lock已经被上锁了,就会堵塞在这里,指到lock解锁

block是设置是否堵塞

timout是设置堵塞的最大时间

lock.release()

解锁

lock.locked()

返回lock的状态是否上锁,True或False

 

代码演示:

from threading import Thread,Lock

  

a = 0

  

  def func():

    global a

    #在对全局变量a取值操作之前,给线程上锁

    lock.acquire()

    for i in range(100000):

        a += 1

    #操作完全局变量,解锁

    lock.release()

  if __name__ == '__main__':

    lock = Lock()

    thread1 = Thread(target=func)

    thread2 = Thread(target=func)

    thread1.start()

    thread2.start()

    thread1.join()

    thread2.join()

    print(a)

以同步的方式,最终输出的a = 200000

4.5线程安全队列

         python在queue模块中为我们提供了Queue()作为线程安全队列。

         测试Queue()的安全性:

         quque模块中的Queue使用方法几乎multiprocessing中的Queue和multiprocess.

Manager().Queue()完全一致

        

from threading import Thread

  from queue import Queue

  

queue = Queue()

  #定义个函数,每次执行这个函数,都会向quque队列中加入一百万个数字

  def func():

    global queue

    for i in range(100000):

        queue.put(i)

  

  if __name__ == '__main__':

    #创建100个线程,把线程对象保存在thread_s集合中

    thread_s = []

    for i in range(100):

        thread_s.append(Thread(target=func))

    #开启这100个线程

    for thread in thread_s:

        thread.start()

    #等待所有的子线程执行完成

    for thread in thread_s:

        thread.join()

    #打印queue队列的长度

    print(queue.qsize())

         最终的到队列的长度和所有线程向队列中加入值的数量总和相同,没有发现丢失的情况

        

         在线程高并发的情况下,可以使用Queue()来处理线程通信

 

4.6模拟得到高并发时候,用户访问网站的点击量

        

from threading import Thread

  from queue import Queue

  #全局变量统计网站的点击次数

  click_rate_num = 0

  #用于统计点击量

  def count_click_rate():

    """

    如果可以从queue_click拿到一个值,就把click_rate_rum +1

    队列的最大等待时间是5秒,5秒后仍没有拿到队列数据,就结束循环

    """

    global click_rate_num

    while True:

        try:

            queue_click.get(timeout=5)

            click_rate_num += 1

        except:

            break

  

def user_request():

    for i in range(1000):

        #处理请求

        #给用户响应过去网页资源

        #点击量+1

        queue_click.put(1)

        #释放资源

  

  if __name__ == '__main__':

    #实例化一个用于统计点击量的队列

    queue_click = Queue()

    #每个线程表示不停的在访问网站的用户

    #创建100个这样的线程,保存在user_thread_s集合里面

    user_thread_s = []

    for i in range(100):

        user_thread_s.append(Thread(target=user_request))

    #创建一个符合统计全局点击量的线程,并开启

    count_click_thread = Thread(target=count_click_rate)

    count_click_thread.start()

    #开启模拟用户点击的线程

    for user_thread in user_thread_s:

        user_thread.start()

    #等待所有的用户点击线程都结束了之后,放行主线程

    for user_thread in user_thread_s:

        user_thread.join()

    #等待统计点击量的线程运行完,放行主线程

    count_click_thread.join()

    #打印总共获得的点击量

    print(click_rate_num)

 

 

五、线程的私有变量

         如何让每个线程有自己独立的数据呢:

  1. 函数中的参数,每个函数中自己的参数只在这个函数中有效。
  2. 把子线程要执行的函数封装成一个类对象,每个实例化的类对象的self.的变量是独立的
  3. 使用ThreadLocal

使用threading的local类

 

from threading import Thread,local

  import time

  

  #实例化一个local类对象,每个线程对于这个类的操作都是独立的,互不影响

  thread_local = local()

  

  #初始化一个therad_local_param的值,然后循环打印

  def func1():

    thread_local.param = 'func1'

    while True:

        print('func1函数的thread_local_param=',thread_local.param)

        time.sleep(1)

  

  #初始化一个therad_local_param的值,然后循环打印

  def func2():

    thread_local.param = 'func2'

    while True:

        print('func2函数的thread_local_param=',thread_local.param)

        time.sleep(1)

  

  if __name__ == '__main__':

    thread1 = Thread(target=func1)

    thread2 = Thread(target=func2)

  

    thread1.start()

    thread2.start()

 

 

相关文章: