threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组,线程也不能被停止、暂停、恢复、中断。

1.     threading模块提供的类:

Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

2.     threading 模块提供的常用方法:

  threading.currentThread(): 返回当前的线程变量。

  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

3.     threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

4.     Thread类

  isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。

  get/setName(name): 获取/设置线程名。

  start():  线程准备就绪,等待CPU调度

  is/setDaemon(bool): 获取/设置是后台线程(默认前台线程(False))。(在start之前设置)

    如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,主线程和后台线程均停止

  如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

  start(): 启动线程。

  join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。

  1. #coding=utf-8
  2. import threading
  3. def thread_job():
  4. print('This is a thread of %s' % threading.current_thread())
  5.  
  6. def main():
  7. thread = threading.Thread(target=thread_job,) # 定义线程
  8. thread.start() # 让线程开始工作
  9.  
  10. if __name__ == '__main__':
  11. main()

threading.Thread()

  1. #coding=utf-8
  2. import threading
  3. def thread_job():
  4. print('This is a thread of %s' % threading.current_thread())
  5. def main():
  6. thread = threading.Thread(target=thread_job,) # 定义线程
  7. thread.setDaemon(False)
  8. thread.start() # 让线程开始工作
  9. if __name__ == '__main__':
  10. print("")
  11. main()
  12. print("")
  13. '''output:
  14. 1
  15. 2
  16. This is a thread of <Thread(Thread-1, started 9424)>
  17. '''
  18.  
  19. #coding=utf-8
  20. import threading
  21. def thread_job():
  22. print('This is a thread of %s' % threading.current_thread())
  23. def main():
  24. thread = threading.Thread(target=thread_job,) # 定义线程
  25. thread.setDaemon(True)
  26. thread.start() # 让线程开始工作
  27. if __name__ == '__main__':
  28. print("")
  29. main()
  30. print("")
  31. '''output:
  32. 1
  33. 2
  34. '''

setDaemon

  1. #coding=utf-8
  2. import threading
  3. def thread_job():
  4. print('This is a thread of %s' % threading.current_thread())
  5.  
  6. def main():
  7. thread = threading.Thread(target=thread_job,) # 定义线程
  8. thread.setDaemon(True)
  9. thread.start() # 让线程开始工作
  10. thread.join()
  11. if __name__ == '__main__':
  12. print("")
  13. main()
  14. print("")
  15.  
  16. '''output:
  17. 1
  18. This is a thread of <Thread(Thread-1, started daemon 5292)>
  19. 2
  20. '''
  21.  
  22. #coding=utf-8
  23. import threading
  24. def thread_job():
  25. print('This is a thread of %s' % threading.current_thread())
  26.  
  27. def main():
  28. thread = threading.Thread(target=thread_job,) # 定义线程
  29. #thread.setDaemon(True)
  30. thread.start() # 让线程开始工作
  31. thread.join()
  32. if __name__ == '__main__':
  33. print("")
  34. main()
  35. print("")
  36. '''output:
  37. 1
  38. This is a thread of <Thread(Thread-1, started 9220)>
  39. 2
  40. '''
  41.  
  42. #coding=utf-8
  43. import threading
  44. def thread_job():
  45. print('This is a thread of %s' % threading.current_thread())
  46.  
  47. def main():
  48. thread = threading.Thread(target=thread_job,) # 定义线程
  49. #thread.setDaemon(True)
  50. thread.start() # 让线程开始工作
  51. #thread.join()
  52. if __name__ == '__main__':
  53. print("")
  54. main()
  55. print("")
  56. '''output:
  57. 1
  58. 2
  59. This is a thread of <Thread(Thread-1, started 10032)>
  60. '''

join

1、setDaemon默认是False,无论主程序是否执行完,子程序必须都要执行完,只有等主、子程序都执行完才会退出程序(先后关系不一定是按代码至上向下执行---非阻塞式。)

  1. import threading,time
  2. def job1():
  3. global A
  4. for i in range(2):
  5. A+=1
  6. time.sleep(1)
  7. print('job1',A)
  8. def job2():
  9. global A
  10. for i in range(2):
  11. A+=10
  12. time.sleep(2)
  13. print('job2',A)
  14. if __name__== '__main__':
  15. print("begin")
  16. lock=threading.Lock()
  17. A=0
  18. t1=threading.Thread(target=job1)
  19. t2=threading.Thread(target=job2)
  20. t1.start()
  21. t2.start()
  22. print("end")
  23.  
  24. '''output:
  25. begin
  26. end
  27. ('job1', 11)
  28. ('job2', 12)
  29. ('job1', 22)
  30. ('job2', 22)
  31. '''

setDaemon

2、setDaemon默认是True,无论子程序是否执行完,主程序只要执行完就会退出程序。(先后关系不一定是按代码至上向下执行---非阻塞式。)

  1. import threading,time
  2. def job1():
  3. global A
  4. for i in range(2):
  5. A+=1
  6. time.sleep(1)
  7. print('job1',A)
  8. def job2():
  9. global A
  10. for i in range(2):
  11. A+=10
  12. time.sleep(2)
  13. print('job2',A)
  14. if __name__== '__main__':
  15. print("begin")
  16. lock=threading.Lock()
  17. A=0
  18. t1=threading.Thread(target=job1)
  19. t2=threading.Thread(target=job2)
  20. t1.setDaemon(True)
  21. t2.setDaemon(True)
  22. t1.start()
  23. t2.start()
  24. print("end")
  25.  
  26. '''output:
  27. begin
  28. end
  29. '''

setDaemon

3、join方法存按代码至上而下先后执行(必须得执行完中间代码的子线程,才会去执行末行主线程代码)---阻塞式

同一线程对象的start和join是否紧挨着区别:

  1. import threading,time
  2. def job1():
  3. print("start the 1st threading")
  4. time.sleep(0.5)
  5. print("1st threading ends")
  6. def job2():
  7. print("start the 2nd threading")
  8. time.sleep(2)
  9. print("2nd threading ends")
  10. def job3():
  11. print("start the 3rd threading")
  12. time.sleep(1)
  13. print("3rd threading ends")
  14. if __name__== '__main__':
  15. print("begins")
  16. lock=threading.Lock()
  17. first = threading.Thread(target=job1)
  18. second = threading.Thread(target=job2)
  19. third = threading.Thread(target=job3)
  20. first.start()
  21. first.join()
  22. second.start()
  23. second.join()
  24. third.start()
  25. third.join()
  26. print("ends")
  27. '''输出:
  28. begins
  29. start the 1st threading
  30. 1st threading ends
  31. start the 2nd threading
  32. 2nd threading ends
  33. start the 3rd threading
  34. 3rd threading ends
  35. ends
  36. '''

同个线程对象的start和join紧挨着

  1. import threading,time
  2. def job1():
  3. print("start the 1st threading")
  4. time.sleep(0.5)
  5. print("1st threading ends")
  6. def job2():
  7. print("start the 2nd threading")
  8. time.sleep(2)
  9. print("2nd threading ends")
  10. def job3():
  11. print("start the 3rd threading")
  12. time.sleep(1)
  13. print("3rd threading ends")
  14. if __name__== '__main__':
  15. print("begins")
  16. lock=threading.Lock()
  17. first = threading.Thread(target=job1)
  18. second = threading.Thread(target=job2)
  19. third = threading.Thread(target=job3)
  20. first.start()
  21. second.start()
  22. third.start()
  23. first.join()
  24. second.join()
  25. third.join()
  26. print("ends")
  27.  
  28. '''输出:
  29. begins
  30. start the 1st threading
  31. start the 2nd threading
  32. start the 3rd threading
  33. 1st threading ends
  34. 3rd threading ends
  35. 2nd threading ends
  36. ends
  37. '''

同一个线程对象的start和join不紧挨着

小结:个人感觉同一线程对象的start和join最好不要紧挨着。

5、Queue

在多线程函数中定义一个Queue,用来保存返回值,代替return,定义一个多线程列表,初始化一个多维数据列表。

  1. import threading
  2. import time
  3.  
  4. from queue import Queue
  5.  
  6. def job(l,q):
  7. for i in range (len(l)):
  8. l[i] = l[i]**2
  9. q.put(l)
  10.  
  11. def multithreading():
  12. q =Queue()
  13. threads = []
  14. data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
  15. for i in range(4):
  16. t = threading.Thread(target=job,args=(data[i],q))
  17. t.start()
  18. threads.append(t)
  19. for thread in threads:
  20. thread.join()
  21. results = []
  22. for _ in range(4):
  23. results.append(q.get())
  24. print(results)
  25.  
  26. if __name__=='__main__':
  27. multithreading()

Queue

6、线程锁

lock在不同线程使用同一共享内存时,能够确保线程之间互不影响,使用lock的方法是, 在每个线程执行运算修改共享内存之前,执行lock.acquire()将共享内存上锁, 确保当前线程执行时,内存不会被其他线程访问,执行运算完毕后,使用lock.release()将锁打开, 保证其他的线程可以使用该共享内存。

当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

  1. import threading,time
  2. def job1():
  3. global A
  4. for i in range(2):
  5. print("get A_value of job1",A)
  6. A+=1
  7. print("A + 1 = %d"%(A))
  8. time.sleep(1)
  9. print("get A_value of job1",A)
  10. def job2():
  11. global A
  12. for i in range(2):
  13. print("get A_value of job2",A)
  14. A+=10
  15. print("A + 10 = %d"%(A))
  16. time.sleep(3)
  17. print("get A_value of job2",A)
  18. if __name__== '__main__':
  19. print("begin")
  20. lock=threading.Lock()
  21. A=0
  22. t1=threading.Thread(target=job1)
  23. t2=threading.Thread(target=job2)
  24. t1.start()
  25. t2.start()
  26. t1.join()
  27. t2.join()
  28. print("end")
  29. '''输出:
  30. begin
  31. ('get A_value of job1', 0)
  32. A + 1 = 1
  33. ('get A_value of job2', 1)
  34. A + 10 = 11
  35. ('get A_value of job1', 11)
  36. A + 1 = 12
  37. ('get A_value of job1', 12)
  38. ('get A_value of job2', 12)
  39. A + 10 = 22
  40. ('get A_value of job2', 22)
  41. end
  42. '''

无线程锁

  1. import threading,time
  2. def job1():
  3. global A,lock
  4. lock.acquire()
  5. for i in range(2):
  6. print("get A_value of job1",A)
  7. A+=1
  8. print("A + 1 = %d"%(A))
  9. time.sleep(1)
  10. print("get A_value of job1",A)
  11. lock.release()
  12. def job2():
  13. global A,lock
  14. lock.acquire()
  15. for i in range(2):
  16. print("get A_value of job2",A)
  17. A+=10
  18. print("A + 10 = %d"%(A))
  19. time.sleep(3)
  20. print("get A_value of job2",A)
  21. lock.release()
  22. if __name__== '__main__':
  23. print("begin")
  24. lock=threading.Lock()
  25. A=0
  26. t1=threading.Thread(target=job1)
  27. t2=threading.Thread(target=job2)
  28. t1.start()
  29. t2.start()
  30. t1.join()
  31. t2.join()
  32. print("end")
  33. '''输出:
  34. begin
  35. ('get A_value of job1', 0)
  36. A + 1 = 1
  37. ('get A_value of job1', 1)
  38. A + 1 = 2
  39. ('get A_value of job1', 2)
  40. ('get A_value of job2', 2)
  41. A + 10 = 12
  42. ('get A_value of job2', 12)
  43. A + 10 = 22
  44. ('get A_value of job2', 22)
  45. end
  46. '''

有线程锁

7、死锁

死锁现象例子:共享资源A,B;锁lock1,lock2;两个线程threading1,threading2。

个人感觉就是锁中锁:

    一个线程threading1里一个锁lock1还没释放A资源(也就是lock1已锁定了A),就想获取另一个锁lock2来得到B资源(也就是想锁定B),所以在等待B资源释放;

    一个线程threading2里一个锁lock1还没释放B资源(也就是lock1已锁定了B),就想获取另一个锁lock2来得到A资源(也就是想锁定A),所以在等待A资源释放;

这样因两个线程相互等待资源释放就造成死锁。

  1. #coding=utf-8
  2. import threading,time
  3. def job1():
  4. global A,B,lock1,lock2
  5. lock1.acquire()
  6. for i in range(2):
  7. print("获取job1中: A = %d"%(A))
  8. A+=1
  9. print("A + 1 = %d"%(A))
  10. time.sleep(1)
  11. print("获取job1中: A = %d"%(A))
  12. lock2.acquire()
  13. for i in range(2):
  14. print("获取job1中: B = %d"%(B))
  15. B+=10
  16. print("B + 10 = %d"%(B))
  17. time.sleep(3)
  18. print("获取job1中: B = %d"%(B))
  19. lock2.release()
  20. lock1.release()
  21. def job2():
  22. global A,B,lock1,lock2
  23. lock2.acquire()
  24. for i in range(2):
  25. print("获取job2中: B = %d"%(B))
  26. B+=1
  27. print("B + 10 = %d"%(B))
  28. time.sleep(3)
  29. print("获取job2中: B = %d"%(B))
  30. lock1.acquire()
  31. for i in range(2):
  32. print("获取job1中: A = %d"%(A))
  33. A+=10
  34. print("A + 1 = %d"%(A))
  35. time.sleep(1)
  36. print("获取job1中: A = %d"%(A))
  37. lock1.release()
  38. lock2.release()
  39. if __name__== '__main__':
  40. print("begin")
  41. lock1=threading.Lock()
  42. lock2=threading.Lock()
  43. A=B=0
  44. t1=threading.Thread(target=job1)
  45. t2=threading.Thread(target=job2)
  46. t1.start()
  47. t2.start()
  48. t1.join()
  49. t2.join()
  50. print("end")

死锁

小结:无论A是否等于B,lock1是否等于lock2,感觉只要存在锁中锁(每个线程里已acquire了还未释放又进行acquire)就会产生死锁。

8、递归锁:

  1. RLock本身有一个计数器,如果碰到acquire,那么计数器+1;如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1;直到如果计数器等于0,才会去执行下一个线程。
  1. #coding=utf-8
  2. import threading,time
  3. def job1():
  4. global A,B,lock1,lock2
  5. lock1.acquire()
  6. for i in range(2):
  7. print("获取job1中: A = %d"%(A))
  8. A+=1
  9. print("A + 1 = %d"%(A))
  10. time.sleep(1)
  11. print("获取job1中: A = %d"%(A))
  12. lock2.acquire()
  13. for i in range(2):
  14. print("获取job1中: B = %d"%(B))
  15. B+=10
  16. print("B + 10 = %d"%(B))
  17. time.sleep(3)
  18. print("获取job1中: B = %d"%(B))
  19. lock2.release()
  20. lock1.release()
  21. def job2():
  22. global A,B,lock1,lock2
  23. lock2.acquire()
  24. for i in range(2):
  25. print("获取job2中: B = %d"%(B))
  26. B+=1
  27. print("B + 10 = %d"%(B))
  28. time.sleep(3)
  29. print("获取job2中: B = %d"%(B))
  30. lock1.acquire()
  31. for i in range(2):
  32. print("获取job1中: A = %d"%(A))
  33. A+=10
  34. print("A + 1 = %d"%(A))
  35. time.sleep(1)
  36. print("获取job1中: A = %d"%(A))
  37. lock1.release()
  38. lock2.release()
  39. if __name__== '__main__':
  40. print("begin")
  41. lock1=lock2=threading.RLock()
  42. A=B=0
  43. t1=threading.Thread(target=job1)
  44. t2=threading.Thread(target=job2)
  45. t1.start()
  46. t2.start()
  47. t1.join()
  48. t2.join()
  49. print("end")
  50. '''输出:
  51. begin
  52. 获取job1中: A = 0
  53. A + 1 = 1
  54. 获取job1中: A = 1
  55. A + 1 = 2
  56. 获取job1中: A = 2
  57. 获取job1中: B = 0
  58. B + 10 = 10
  59. 获取job1中: B = 10
  60. B + 10 = 20
  61. 获取job1中: B = 20
  62. 获取job2中: B = 20
  63. B + 10 = 21
  64. 获取job2中: B = 21
  65. B + 10 = 22
  66. 获取job2中: B = 22
  67. 获取job1中: A = 2
  68. A + 1 = 12
  69. 获取job1中: A = 12
  70. A + 1 = 22
  71. 获取job1中: A = 22
  72. end
  73. '''

threading.Rlock()

每一个线程job里必须只有一个Rlock对象,有不同的Rlock对象也会产生死锁。如下:

  1. #coding=utf-8
  2. import threading,time
  3. def job1():
  4. global A,B,lock1,lock2
  5. lock1.acquire()
  6. for i in range(2):
  7. print("获取job1中: A = %d"%(A))
  8. A+=1
  9. print("A + 1 = %d"%(A))
  10. time.sleep(1)
  11. print("获取job1中: A = %d"%(A))
  12. lock2.acquire()
  13. for i in range(2):
  14. print("获取job1中: B = %d"%(B))
  15. B+=10
  16. print("B + 10 = %d"%(B))
  17. time.sleep(3)
  18. print("获取job1中: B = %d"%(B))
  19. lock2.release()
  20. lock1.release()
  21. def job2():
  22. global A,B,lock1,lock2
  23. lock2.acquire()
  24. for i in range(2):
  25. print("获取job2中: B = %d"%(B))
  26. B+=1
  27. print("B + 10 = %d"%(B))
  28. time.sleep(3)
  29. print("获取job2中: B = %d"%(B))
  30. lock1.acquire()
  31. for i in range(2):
  32. print("获取job1中: A = %d"%(A))
  33. A+=10
  34. print("A + 1 = %d"%(A))
  35. time.sleep(1)
  36. print("获取job1中: A = %d"%(A))
  37. lock1.release()
  38. lock2.release()
  39. if __name__== '__main__':
  40. print("begin")
  41. lock1=threading.RLock()
  42. lock2=threading.RLock()
  43. A=B=0
  44. t1=threading.Thread(target=job1)
  45. t2=threading.Thread(target=job2)
  46. t1.start()
  47. t2.start()
  48. t1.join()
  49. t2.join()
  50. print("end")

9、GIL

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL。
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

多线程threading的更多相关文章

  1. python多线程threading.Lock锁用法实例

    本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 mutex = threading.Lock() #锁 ...

  2. Python初学——多线程Threading

    接着上篇继续跟着沫凡小哥学Python啦 1.1 什么是多线程 Threading 多线程可简单理解为同时执行多个任务. 多进程和多线程都可以执行多个任务,线程是进程的一部分.线程的特点是线程之间可以 ...

  3. 多线程-threading

    多线程-threading python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用 1. 使用threading模块 ...

  4. python的多线程threading

    多线程threading 1.Thread创建线程: 上代码: #!/usr/bin/env python3 import threading import time def A(): t_name ...

  5. 多进程 multiprocessing 多线程Threading 线程池和进程池concurrent.futures

    multiprocessing.procsess 定义一个函数 def func():pass 在if __name__=="__main__":中实例化 p = process( ...

  6. python中多进程multiprocessing、多线程threading、线程池threadpool

    浅显点理解:进程就是一个程序,里面的线程就是用来干活的,,,进程大,线程小 一.多线程threading 简单的单线程和多线程运行:一个参数时,后面要加逗号 步骤:for循环,相当于多个线程——t=t ...

  7. 物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_221 一般情况下,大家对Python原生的并发/并行工作方式:进程.线程和协程的关系与区别都能讲清楚.甚至具体的对象名称.内置方法 ...

  8. python多线程threading

    本文通过 4个example 介绍python中多线程package —— threading的常用用法, 包括调用多线程, 同步队列类Queue, Ctrl+c结束多线程. example1. 调用 ...

  9. Python_多线程threading模块

    python 在执行的时候会淡定的在CPU上只允许一个线程运行,故Python在多核CPU的情况下也只能发挥出单核的功能,其中的原因:gil锁 gil 锁 (全局解释器锁):每个线程在执行时都需要先获 ...

随机推荐

  1. 【转载】java 中变量的存储位置

    原文链接点这里,感谢博主分享 * 寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. * 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出 ...

  2. 前端javascript如何阻止按下退格键页面回退 但 不阻止文本框使用退格键删除文本

    这段代码可以: document.onkeydown = function (e) { e.stopPropagation(); // 阻止事件冒泡传递 e.preventDefault(); // ...

  3. MyBatis之整合Spring

    MyBatis之整合Spring 整合思路: 1.SqlSessionFactory对象应该放到spring容器中作为单例存在 2.传统dao的开发方式中,应该从spring容器中获得sqlSessi ...

  4. Django用户继承AbstractUser后密码为明文

    Django用户继承AbstractUser后密码为明文 其实本不应该有这个问题,却花了我很久的时间,因为还是初学阶段. 造成这个原因是因为在admin注册的生活没有指定Admin 在app的admi ...

  5. Dynamics 365-关于Solution的那些事(二)

    接着上一篇的说,现在有一个已知前提:Solution的增量特性.然后我们再思考这么一个场景,项目开发过程中,存在多次迭代的情况,每次迭代可能涉及到的solution是同一个,唯一区别的,就是solut ...

  6. netstat -an查看到大量的TIME_WAIT状态的解决办法

    netstat下time_wait状态的tcp连接: 1.这是一种处于连接完全关闭状态前的状态: 2.通常要等上4分钟(windows server)的时间才能完全关闭: 3.这种状态下的tcp连接占 ...

  7. Go 编译原理实现计算器(测试驱动讲解)

    本文不需要你掌握任何编译原理的知识. 只需要看懂简单的golang语言即可, 完整的代码示例在GIT, 代码是从writing an interpreter in go这本书抽取了简单的部分出来, 如 ...

  8. python之list和tuple

    https://www.cnblogs.com/evablogs/p/6691743.html list和tuple区别: 相同:均为有序集合 异同:list可变,tuple一旦初始化则不可变 lis ...

  9. Azure导出所有用户权限---powershell命令

      直接运行脚本         #requires -Version 3.0 -Modules AzureRM.Resourcesparam(    [switch]    $GroupRolesB ...

  10. 2013年山东省赛F题 Mountain Subsequences

    2013年山东省赛F题 Mountain Subsequences先说n^2做法,从第1个,(假设当前是第i个)到第i-1个位置上哪些比第i位的小,那也就意味着a[i]可以接在它后面,f1[i]表示从 ...