zoukankan      html  css  js  c++  java
  • python成长之路 :线程、进程和协程

    python线程

    进程与线程的历史

          我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心,它承担计算机的所有任务。 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等。 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构——进程控制 块。 进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进程控制块三部分组成。我们编 写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化 过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

          在早期的操作系统里,计算机只有一个核心,进程执行程序的最小单位,任务调度采用时间片轮转的抢占式方式进行进程调度。每个进程都有各自的一块独立的内 存,保证进程彼此间的内存地址空间的隔离。 随着计算机技术的发展,进程出现了很多弊端,一是进程的创建、撤销和切换的开销比较大,二是由于对称多处理机(对称多处理机 (SymmetricalMulti-Processing)又叫SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统 以及总线结构)的出现,可以满足多个运行单位,而多进程并行开销过大。 这个时候就引入了线程的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。 线程没有自己的系统资源,只拥有在运行时必不可少的资源。但线程可以与同属与同一进程的其他线程共享进程所拥有的其他资源。

    进程与线程之间的关系

    线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同 一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import threading
     4 import time,datetime
     5 
     6 def show(arg):
     7     time.sleep(5)
     8     print('thread'+str(arg),time.time())
     9 
    10 
    11 for i in range(4):
    12     t = threading.Thread(target=show, args=(i,))
    13 #    t.setName('name%s' % i)
    14 #    print(t.name)
    15     #t.setDaemon(False) #默认是False, 如果改成True,主线程不等待子线程
    16 
    17     t.start()
    18     t.run()  #立即执行
    19     t.join(2)
    20 print('main thread stop')

    上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

    更多方法:

      • start            线程准备就绪,等待CPU调度
      • setName      为线程设置名称
      • getName      获取线程名称
      • setDaemon   设置为后台线程或前台线程(默认为False)
                           如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                            如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
      • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
      • run              线程被cpu调度后自动执行线程对象的run方法

    线程锁threading.RLock和threading.Lock

    我们使用线程对数据进行操作的时候,如果多个线程同时修改某个数据,可能会出现不可预料的结果,为了保证数据的准确性,引入了锁的概念

    例:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1,那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。

     1 #未加锁
     2 import threading
     3 import time
     4 
     5 gl_num = 0
     6 
     7 def show(arg):
     8     global gl_num
     9     time.sleep(1)
    10     gl_num += 1
    11     print(gl_num)
    12 
    13 for i in range(10):
    14     t = threading.Thread(target=show, args=(i,))
    15     t.start()
    16 
    17 print('main thread stop')
    未加锁代码
     1 线程锁
     2 import threading
     3 import time
     4 
     5 gl_num = 0
     6 
     7 lock = threading.RLock()  #获取一个锁的对象
     8 
     9 def Func():
    10     lock.acquire() #创建一把锁
    11     global gl_num  #声明全局变量
    12     gl_num +=1
    13     time.sleep(1)
    14     print(gl_num)
    15     lock.release()  #更改完释放锁
    16 
    17 for i in range(10):
    18     t = threading.Thread(target=Func)  #利用线程执行,执行10次 结果1,2,3,4,5,6.。
    19     t.start()
    线程锁

    threading.Event

    Event是线程间通信最间的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。用于主线程控制其他线程的执行。 Events 管理一个flag,这个flag可以使用set()设置成True或者使用clear()重置为False,wait()则用于阻塞,在flag为 True之前。flag默认为False。

    • Event.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。
    • Event.set() :将标识位设为Ture
    • Event.clear() : 将标识伴设为False。
    • Event.isSet() :判断标识位是否为Ture。
     1 event
     2 import threading
     3 
     4 
     5 def do(event):  #定义一个函数,传递参数envent,其实就是传递了一个对象
     6     print 'start'
     7     event.wait()  #True则不阻塞
     8     print 'execute'
     9 
    10 
    11 event_obj = threading.Event()
    12 for i in range(10):
    13     t = threading.Thread(target=do, args=(event_obj,))  #传递执行函数和参数
    14     t.start()
    15 
    16 event_obj.clear()  #将“Flag”设置为False
    17 inp = raw_input('input:')
    18 if inp == 'true':
    19     event_obj.set()  #set默认为True
    event

    queue模块

    Queue 就是对队列,它是线程安全的

    举例来说,我们去肯德基吃饭。厨房是给我们做饭的地方,前台负责把厨房做好的饭卖给顾客,顾客则去前台领取做好的饭。这里的前台就相当于我们的队列。

    这个模型也叫生产者-消费者模型。

    复制代码
    
    import queue
    
    q = queue.Queue(maxsize=0)  # 构造一个先进显出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。
    
    q.join()    # 等到队列为kong的时候,在执行别的操作
    q.qsize()   # 返回队列的大小 (不可靠)
    q.empty()   # 当队列为空的时候,返回True 否则返回False (不可靠)
    q.full()    # 当队列满的时候,返回True,否则返回False (不可靠)
    q.put(item, block=True, timeout=None) #  将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置,
                             为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后,
                              如果队列无法给出放入item的位置,则引发 queue.Full 异常
    q.get(block=True, timeout=None) #   移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,
                          若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。
    q.put_nowait(item) #   等效于 put(item,block=False)
    q.get_nowait() #    等效于 get(item,block=False)
    

     生产者--消费者:

     1 #!/usr/bin/env python
     2 import Queue
     3 import threading
     4 
     5 
     6 message = Queue.Queue(10)
     7 
     8 
     9 def producer(i):
    10     while True:
    11         message.put(i)
    12 
    13 
    14 def consumer(i):
    15     while True:
    16         msg = message.get()
    17 
    18 
    19 for i in range(12):
    20     t = threading.Thread(target=producer, args=(i,))
    21     t.start()
    22 
    23 for i in range(10):
    24     t = threading.Thread(target=consumer, args=(i,))
    25     t.start()
    生产者消费者模型

    线程池

      1 import queue
      2 import threading
      3 import contextlib
      4 import time
      5 
      6 StopEvent = object()
      7 
      8 
      9 class ThreadPool(object):
     10 
     11     def __init__(self, max_num):
     12         self.q = queue.Queue()
     13         self.max_num = max_num
     14 
     15         self.terminal = False
     16         self.generate_list = []
     17         self.free_list = []
     18 
     19     def run(self, func, args, callback=None):
     20         """
     21         线程池执行一个任务
     22         :param func: 任务函数
     23         :param args: 任务函数所需参数
     24         :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
     25         :return: 如果线程池已经终止,则返回True否则None
     26         """
     27 
     28         if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
     29             self.generate_thread()
     30         w = (func, args, callback,)
     31         self.q.put(w)
     32 
     33     def generate_thread(self):
     34         """
     35         创建一个线程
     36         """
     37         t = threading.Thread(target=self.call)
     38         t.start()
     39 
     40     @contextlib.contextmanager
     41     def worker_state(self, xxx, val):
     42         xxx.append(val)
     43         try:
     44             yield
     45         finally:
     46             xxx.remove(val)
     47 
     48     def call(self):
     49         """
     50         循环去获取任务函数并执行任务函数
     51         """
     52         current_thread = threading.currentThread
     53         self.generate_list.append(current_thread)
     54 
     55         event = self.q.get()
     56         while event != StopEvent:
     57 
     58             func, arguments, callback = event
     59             try:
     60                 result = func(*arguments)
     61                 status = True
     62             except Exception as e:
     63                 status = False
     64                 result = e
     65 
     66             if callback is not None:
     67                 try:
     68                     callback(status, result)
     69                 except Exception as e:
     70                     pass
     71 
     72             if self.terminal: # False
     73                 event = StopEvent
     74             else:
     75                 # self.free_list.append(current_thread)
     76                 # event = self.q.get()
     77                 # self.free_list.remove(current_thread)
     78                 with self.worker_state(self.free_list, current_thread):
     79                     event = self.q.get()
     80         else:
     81             self.generate_list.remove(current_thread)
     82 
     83     def close(self):
     84         num = len(self.generate_list)
     85         while num:
     86             self.q.put(StopEvent)
     87             num -= 1
     88 
     89     # 终止线程(清空队列)
     90     def terminate(self):
     91 
     92         self.terminal = True
     93 
     94         while self.generate_list:
     95             self.q.put(StopEvent)
     96         self.q.empty()
     97 import time
     98 
     99 def work(i):
    100     print(i,"----")
    101 
    102 pool = ThreadPool(10)
    103 for item in range(50):
    104     pool.run(func=work, args=(item,))
    105 
    106 pool.terminate()
    完整版线程池
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import queue
     4 import threading
     5 import contextlib
     6 import time
     7 
     8 StopEvent = object()  #定义标记的意义在于任务结束后退出的标记 
     9 
    10 
    11 class ThreadPool(object):
    12 
    13     def __init__(self, max_num):
    14         self.q = queue.Queue() #定义队列无限大
    15         self.max_num = max_num  #定义最大值
    16 
    17         self.terminal = False  #定义为false
    18         self.generate_list = [] #多少个进程正在执行
    19         self.free_list = []   #定义空闲列表--空闲线程  初始化各种属性
    20 
    21     def run(self, func, args, callback=None):
    22         """
    23         线程池执行一个任务
    24         :param func: 任务函数
    25         :param args: 任务函数所需参数
    26         :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
    27         :return: 如果线程池已经终止,则返回True否则None
    28         """
    29 
    30         if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
    31             self.generate_thread()
    32         w = (func, args, callback,)
    33         self.q.put(w)
    34 
    35     def generate_thread(self):
    36         """
    37         创建一个线程
    38         """
    39         t = threading.Thread(target=self.call)#并执行call方法
    40         t.start()
    41 
    42     def call(self):
    43         """
    44         循环去获取任务函数并执行任务函数
    45         """
    46         current_thread = threading.currentThread #拿到当前线程
    47         self.generate_list.append(current_thread) #添加到正在使用线程队列
    48 
    49         event = self.q.get() #--这里是一个获取到的元组w=()....
    50         while event != StopEvent:
    51 
    52             func, arguments, callback = event # w= (func, args, callback)
    53             try:
    54                 result = func(*arguments)  #执行任务  ret = aaa() --def aaa():   return 1
    55                 status = True      
    56             except Exception as e: #如果我这个任务报错
    57                 status = False
    58                 result = e
    59 
    60             if callback is not None:  #这是一个返回值
    61                 try:
    62                     callback(status, result)
    63                 except Exception as e:
    64                     pass
    65 
    66             self.free_list.append(current_thread)  #---记得看上下文代码,执行完任务,把这个线程放到空闲队列里面
    67             event = self.q.get()#当前的状态应该是没任务,线程等待任务,不结束
    68             self.free_list.remove(current_thread)  #获取任务移除休息线程
    69         else:
    70             if event = StopEvent:
    71             self.generate_list.remove(current_thread) #移除当前正在运行的线程,等他运行完
    72 
    73     def close(self):
    74         num = len(self.generate_list)
    75         while num:
    76             self.q.put(StopEvent)#
    77             num -= 1
    78 
    79 
    80 import time
    81 
    82 def work(i):
    83     print(i)
    84 
    85 pool = ThreadPool(10)   #定义最大线程为10个,实例化,并初始化
    86 for item in range(50):  #创建了50个任务
    87     pool.run(func=work, args=(item,))  #执行work函数,args是传参
    88 
    89 pool.close() #关闭线程池
    理解注释版线程池

     

    Python 进程

     1 from multiprocessing import Process
     2 import threading
     3 import time
     4   
     5 def foo(i):
     6     print 'say hi',i
     7   
     8 for i in range(10):
     9     p = Process(target=foo,args=(i,))
    10     p.start()

    注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

    multiprocessing模块

    multiprocessing是python的多进程管理包,和threading.Thread类似。直接从侧面用subprocesses替换线程使用GIL的方式,由于这一点,multiprocessing模块可以让程序员在给定的机器上充分的利用CPU。

    在multiprocessing中,通过创建Process对象生成进程,然后调用它的start()方法,

    各种参数的意思:

    注意:

      start() 开始进程

      setDaemon(True):守护进程:mutilprocess.setDaemon(True)

      terminate():退出进程:最好使用 poison pill,强制的使用terminate()

      is_alive(): 进程创建之后,可以使用multiprocessing对象的is_alive方法查看线程是否运行,为True则运行

      join():阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。|  或者也可以理解为: 进程池中进程执行完毕后在关闭, 如果注释, 那么程序直接关闭

    守护进程


    守护进程就是不阻挡主程序退出,自己干自己的 mutilprocess.setDaemon(True)

    就这句

    等待守护进程退出,要加上join,join可以传入浮点数值,等待n久就不等了

    import multiprocessing
    import time
    import sys
    
    def daemon():
        name = multiprocessing.current_process().name
        print 'Starting:', name
        time.sleep(2)
        print 'Exiting :', name
    
    def non_daemon():
        name = multiprocessing.current_process().name
        print 'Starting:', name
        print 'Exiting :', name
    
    if __name__ == '__main__':
        d = multiprocessing.Process(name='daemon',
                                    target=daemon)
        d.daemon = True
    
        n = multiprocessing.Process(name='non-daemon',
                                    target=non_daemon)
        n.daemon = False
    
        d.start()
        n.start()
    
        d.join(1)
        print 'd.is_alive()', d.is_alive()
        n.join()
    View Code

    终止进程


    最好使用 poison pill,强制的使用terminate()

    注意 terminate之后要join,使其可以更新状态

    import multiprocessing
    import time
    
    def slow_worker():
        print 'Starting worker'
        time.sleep(0.1)
        print 'Finished worker'
    
    if __name__ == '__main__':
        p = multiprocessing.Process(target=slow_worker)
        print 'BEFORE:', p, p.is_alive()
    
        p.start()
        print 'DURING:', p, p.is_alive()
    
        p.terminate()
        print 'TERMINATED:', p, p.is_alive()
    
        p.join()
        print 'JOINED:', p, p.is_alive()

    简单示例:


    1 from multiprocessing import Process
    2  
    3 def f(name):
    4     print('hello', name)
    5  
    6 if __name__ == '__main__':
    7     p = Process(target=f, args=('bob',))
    8     p.start()
    9     p.join()

    join()方法的示例:


    from multiprocessing import Process
    import os, time, random
    
    def r1(process_name):
        for i in range(5):
            print process_name, os.getpid()     #打印出当前进程的id
            time.sleep(random.random())
    def r2(process_name):
        for i in range(5):
            print process_name, os.getpid()     #打印出当前进程的id
            time.sleep(random.random())
    
    if __name__ == "__main__":
            print "main process run..."
            p1 = Process(target=r1, args=('process_name1', )) 
            p2 = Process(target=r2, args=('process_name2', )) 
    
            p1.start()
            p2.start()
            #p1.join()
            #p2.join()    
            print "main process runned all lines..."

    进程数据共享

    进程各自持有一份数据,默认无法共享数据

     1 #!/usr/bin/env python
     2 #coding:utf-8
     3  
     4 from multiprocessing import Process
     5 from multiprocessing import Manager
     6  
     7 import time
     8  
     9 li = []
    10  
    11 def foo(i):
    12     li.append(i)
    13     print 'say hi',li
    14   
    15 for i in range(10):
    16     p = Process(target=foo,args=(i,))
    17     p.start()
    18      
    19 print 'ending',li
    进程间无数据共享
     1 #方法一,Array
     2 from multiprocessing import Process,Array
     3 temp = Array('i', [11,22,33,44])
     4  
     5 def Foo(i):
     6     temp[i] = 100+i
     7     for item in temp:
     8         print i,'----->',item
     9  
    10 for i in range(2):
    11     p = Process(target=Foo,args=(i,))
    12     p.start()
    13  
    14 #方法二:manage.dict()共享数据
    15 from multiprocessing import Process,Manager
    16  
    17 manage = Manager()
    18 dic = manage.dict()
    19  
    20 def Foo(i):
    21     dic[i] = 100+i
    22     print dic.values()
    23  
    24 for i in range(2):
    25     p = Process(target=Foo,args=(i,))
    26     p.start()
    27     p.join()
    1     'c': ctypes.c_char,  'u': ctypes.c_wchar,
    2     'b': ctypes.c_byte,  'B': ctypes.c_ubyte,
    3     'h': ctypes.c_short, 'H': ctypes.c_ushort,
    4     'i': ctypes.c_int,   'I': ctypes.c_uint,
    5     'l': ctypes.c_long,  'L': ctypes.c_ulong,
    6     'f': ctypes.c_float, 'd': ctypes.c_double

    当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 
     4 from multiprocessing import Process, Array, RLock
     5 
     6 def Foo(lock,temp,i):
     7     """
     8     将第0个数加100
     9     """
    10     lock.acquire()
    11     temp[0] = 100+i
    12     for item in temp:
    13         print i,'----->',item
    14     lock.release()
    15 
    16 lock = RLock()
    17 temp = Array('i', [11, 22, 33, 44])
    18 
    19 for i in range(20):
    20     p = Process(target=Foo,args=(lock,temp,i,))
    21     p.start()
    进程锁实例

    进程池

    进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

    进程池中有两个方法:

    • apply
    • apply_async
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 from  multiprocessing import Process,Pool
     4 import time
     5   
     6 def Foo(i):
     7     time.sleep(2)
     8     return i+100
     9   
    10 def Bar(arg):
    11     print arg
    12   
    13 pool = Pool(5) #进程池
    14 #print pool.apply(Foo,(1,))
    15 #print pool.apply_async(func =Foo, args=(1,)).get()
    16   
    17 for i in range(10):
    18     pool.apply_async(func=Foo, args=(i,),callback=Bar)#但它是非阻塞且支持结果返回进行回调 (回调 ret = pool.apply....)
    19   
    20 print 'end'
    21 pool.close()#关闭进程池,不再接受新的进程
    22 pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
     1 #apply和apply_async
     2 
     3 
     4 from multiprocessing import Pool
     5 import time
     6 
     7 def f1(a):
     8     time.sleep(1)
     9     print(a)
    10     return 1000
    11 def f2(arg):
    12     print(arg)
    13 
    14 if __name__ == '__main__':
    15     pool = Pool(5)
    16     for i in range(10):
    17         pool.apply_async(func=f1, args=(i,), callback=f2)
    18         #pool.apply(func=f1, args=(i,))
    19         print('1111111')
    20 
    21     pool.close()
    22     pool.join()
    window环境代码

    协程

    线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

    协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

    协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

    greenlet

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3  
     4  
     5 from greenlet import greenlet
     6  
     7  
     8 def test1():
     9     print 12
    10     gr2.switch()
    11     print 34
    12     gr2.switch()
    13  
    14  
    15 def test2():
    16     print 56
    17     gr1.switch()
    18     print 78
    19  
    20 gr1 = greenlet(test1)
    21 gr2 = greenlet(test2)
    22 gr1.switch()

    gevent

     1 import gevent
     2  
     3 def foo():
     4     print('Running in foo')
     5     gevent.sleep(0)
     6     print('Explicit context switch to foo again')
     7  
     8 def bar():
     9     print('Explicit context to bar')
    10     gevent.sleep(0)
    11     print('Implicit context switch back to bar')
    12  
    13 gevent.joinall([
    14     gevent.spawn(foo),
    15     gevent.spawn(bar),
    16 ])

    遇到IO操作自动切换:

     1 from gevent import monkey; monkey.patch_all()
     2 import gevent
     3 import urllib2
     4 
     5 def f(url):
     6     print('GET: %s' % url)
     7     resp = urllib2.urlopen(url)
     8     data = resp.read()
     9     print('%d bytes received from %s.' % (len(data), url))
    10 
    11 gevent.joinall([
    12         gevent.spawn(f, 'https://www.python.org/'),
    13         gevent.spawn(f, 'https://www.yahoo.com/'),
    14         gevent.spawn(f, 'https://github.com/'),
    15 ])
    View Code
  • 相关阅读:
    Linux进程管理工具Supervisor
    RSA加密传输代码示例
    静态网站创建工具Docusaurus
    Proactor和Reactor模型
    机器学习中的七宗罪
    Tokio internals: Understanding Rust's asynchronous I/O framework from the bottom up
    开源软件创建SOC的一份清单
    How to setup SOC using open-source tools
    彼得定律
    深入浅出通信原理连载
  • 原文地址:https://www.cnblogs.com/renfanzi/p/5608805.html
Copyright © 2011-2022 走看看