1. 程式人生 > >介紹Linux下面執行緒的操作、多執行緒的同步和互斥

介紹Linux下面執行緒的操作、多執行緒的同步和互斥

 執行緒?為什麼有了程序還需要執行緒呢,他們有什麼區別?使用執行緒有什麼優勢呢?還有多執行緒程式設計的一些細節問題,如執行緒之間怎樣同步、互斥,這些東西將在本文中介紹。我在某QQ群裡見到這樣一道面試題:

是否熟悉POSIX多執行緒程式設計技術?如熟悉,編寫程式完成如下功能:

1)有一int型全域性變數g_Flag初始值為0;

2) 在主線稱中起動執行緒1,列印“this is thread1”,並將g_Flag設定為1

3) 在主線稱中啟動執行緒2,列印“this is thread2”,並將g_Flag設定為2

4) 執行緒序1需要線上程2退出後才能退出

5) 主執行緒在檢測到g_Flag從1變為2,或者從2變為1的時候退出

我們帶著這題開始這篇文章,結束之後,大家就都會做了。本文的框架如下:

  • 1、程序與執行緒
  • 2、使用執行緒的理由
  • 3、有關執行緒操作的函式
  • 4、執行緒之間的互斥
  • 5、執行緒之間的同步
  • 6、試題最終程式碼

1、程序與執行緒

程序是程式執行時的一個例項,即它是程式已經執行到何種程度的資料結構的彙集。從核心的觀點看,程序的目的就是擔當分配系統資源(CPU時間、記憶體等)的基本單位

執行緒是程序的一個執行流,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。一個程序由幾個執行緒組成(擁有很多相對獨立的執行流的使用者程式共享應用程式的大部分資料結構),執行緒與同屬一個程序的其他的執行緒共享程序所擁有的全部資源。

"程序——資源分配的最小單位,執行緒——程式執行的最小單位"

程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響,而執行緒只是一個程序中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒沒有單獨的地址空間,一個執行緒死掉就等於整個程序死掉,所以多程序的程式要比多執行緒的程式健壯,但在程序切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程序。

2、使用執行緒的理由

從上面我們知道了程序與執行緒的區別,其實這些區別也就是我們使用執行緒的理由。總的來說就是:程序有獨立的地址空間,執行緒沒有單獨的地址空間(同一程序內的執行緒共享程序的地址空間)。(下面的內容摘自Linux下的多執行緒程式設計

使用多執行緒的理由之一是和程序相比,它是一種非常"節儉"的多工操作方式。我們知道,在Linux系統下,啟動一個新的程序必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼段、堆疊段和資料段,這是一種"昂貴"的多工工作方式。而運行於一個程序中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料,啟動一個執行緒所花費的空間遠遠小於啟動一個程序所花費的空間,而且,執行緒間彼此切換所需的時間也遠遠小於程序間切換所需要的時間。據統計,總的說來,一個程序的開銷大約是一個執行緒開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。

使用多執行緒的理由之二是執行緒間方便的通訊機制。對不同程序來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。執行緒則不然,由於同一程序下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用,這不僅快捷,而且方便。當然,資料的共享也帶來其他一些問題,有的變數不能同時被兩個執行緒所修改,有的子程式中宣告為static的資料更有可能給多執行緒程式帶來災難性的打擊,這些正是編寫多執行緒程式時最需要注意的地方。

除了以上所說的優點外,不和程序比較,多執行緒程式作為一種多工、併發的工作方式,當然有以下的優點:

  • 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、選單的操作,而使用多執行緒技術,將耗時長的操作(time consuming)置於一個新的執行緒,可以避免這種尷尬的情況。
  • 使多CPU系統更加有效。作業系統會保證當執行緒數不大於CPU數目時,不同的執行緒運行於不同的CPU上。
  • 改善程式結構。一個既長又複雜的程序可以考慮分為多個執行緒,成為幾個獨立或半獨立的執行部分,這樣的程式會利於理解和修改。

=============================

從函式呼叫上來說,程序建立使用fork()操作;執行緒建立使用clone()操作。Richard Stevens大師這樣說過:

  • fork is expensive. Memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. Current implementations use a technique called copy-on-write, which avoids a copy of the parent's data space to the child until the child needs its own copy. But, regardless of this optimization, fork is expensive.

  • IPC is required to pass information between the parent and child after the fork. Passing information from the parent to the child before the fork is easy, since the child starts with a copy of the parent's data space and with a copy of all the parent's descriptors. But, returning information from the child to the parent takes more work.

Threads help with both problems. Threads are sometimes called lightweight processes since a thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than process creation.

All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem of synchronization.

=============================

3、有關執行緒操作的函式

#include <pthread.h>
 
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
int pthread_join (pthread_t tid, void ** status);
pthread_t pthread_self (void);
int pthread_detach (pthread_t tid);
void pthread_exit (void *status);

pthread_create用於建立一個執行緒,成功返回0,否則返回Exxx(為正數)。

  • pthread_t *tid:執行緒id的型別為pthread_t,通常為無符號整型,當呼叫pthread_create成功時,通過*tid指標返回。
  • const pthread_attr_t *attr:指定建立執行緒的屬性,如執行緒優先順序、初始棧大小、是否為守護程序等。可以使用NULL來使用預設值,通常情況下我們都是使用預設值。
  • void *(*func) (void *):函式指標func,指定當新的執行緒建立之後,將執行的函式。
  • void *arg:執行緒將執行的函式的引數。如果想傳遞多個引數,請將它們封裝在一個結構體中。

pthread_join用於等待某個執行緒退出,成功返回0,否則返回Exxx(為正數)。

  • pthread_t tid:指定要等待的執行緒ID
  • void ** status:如果不為NULL,那麼執行緒的返回值儲存在status指向的空間中(這就是為什麼status是二級指標的原因!這種才引數也稱為“值-結果”引數)。

pthread_self用於返回當前執行緒的ID。

pthread_detach用於是指定執行緒變為分離狀態,就像程序脫離終端而變為後臺程序類似。成功返回0,否則返回Exxx(為正數)。變為分離狀態的執行緒,如果執行緒退出,它的所有資源將全部釋放。而如果不是分離狀態,執行緒必須保留它的執行緒ID,退出狀態直到其它執行緒對它呼叫了pthread_join

程序也是類似,這也是當我們開啟程序管理器的時候,發現有很多僵死程序的原因!也是為什麼一定要有僵死這個程序狀態。

pthread_exit用於終止執行緒,可以指定返回值,以便其他執行緒通過pthread_join函式獲取該執行緒的返回值。

  • void *status:指標執行緒終止的返回值。

知道了這些函式之後,我們試圖來完成本文一開始的問題:

1)有一int型全域性變數g_Flag初始值為0;

2)在主線稱中起動執行緒1,列印“this is thread1”,並將g_Flag設定為1

3)在主線稱中啟動執行緒2,列印“this is thread2”,並將g_Flag設定為2

這3點很簡單嘛!!!不就是呼叫pthread_create建立執行緒。程式碼如下:

/*
 * 1)有一int型全域性變數g_Flag初始值為0;
 *
 * 2)在主線稱中起動執行緒1,列印“this is thread1”,並將g_Flag設定為1
 *
 * 3)在主線稱中啟動執行緒2,列印“this is thread2”,並將g_Flag設定為2
 *
 */
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

int g_Flag=0;

void* thread1(void*);
void* thread2(void*);

/*
 * when program is started, a single thread is created, called the initial thread or main thread.
 * Additional threads are created by pthread_create.
 * So we just need to create two thread in main().
 */
int main(int argc, char** argv)
{
	printf("enter main\n");
	pthread_t tid1, tid2;
	int rc1=0, rc2=0;
	rc2 = pthread_create(&tid2, NULL, thread2, NULL);
	if(rc2 != 0)
		printf("%s: %d\n",__func__, strerror(rc2));

	rc1 = pthread_create(&tid1, NULL, thread1, &tid2);
	if(rc1 != 0)
		printf("%s: %d\n",__func__, strerror(rc1));
	printf("leave main\n");
	exit(0);	
}
/*
 * thread1() will be execute by thread1, after pthread_create()
 * it will set g_Flag = 1;
 */
void* thread1(void* arg)
{
	printf("enter thread1\n");
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	g_Flag = 1;
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	printf("leave thread1\n");
	pthread_exit(0);
}

/*
 * thread2() will be execute by thread2, after pthread_create()
 * it will set g_Flag = 2;
 */
void* thread2(void* arg)
{
	printf("enter thread2\n");
	printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	g_Flag = 2;
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	printf("leave thread2\n");
	pthread_exit(0);
}

這樣就完成了1)、2)、3)這三點要求。編譯執行得如下結果:

[email protected]:~/workspace/pthead_test$ gcc -lpthread test.c

如果程式中使用到了pthread庫中的函式,除了要#include<pthread.h>,在編譯的時候還有加上-lpthread 選項。
[email protected]:~/workspace/pthead_test$ ./a.out
enter main
enter thread2
this is thread2, g_Flag: 0, thread id is 3079588720
this is thread1, g_Flag: 2, thread id is 3079588720
leave thread2
leave main
enter thread1
this is thread1, g_Flag: 2, thread id is 3071196016
this is thread1, g_Flag: 1, thread id is 3071196016
leave thread1
但是執行結果不一定是上面的,還有可能是:

[email protected]:~/workspace/pthead_test$ ./a.out
enter main
leave main
enter thread1
this is thread1, g_Flag: 0, thread id is 3069176688
this is thread1, g_Flag: 1, thread id is 3069176688
leave thread1

或者是:

[email protected]:~/workspace/pthead_test$ ./a.out
enter main
leave main
等等。這也很好理解因為,這取決於主執行緒main函式何時終止,執行緒thread1、thread2是否能夠來得急執行它們的函式。這也是多執行緒程式設計時要注意的問題,因為有可能一個執行緒會影響到整個程序中的所有其它執行緒!如果我們在main函式退出前,sleep()一段時間,就可以保證thread1、thread2來得及執行。

吸血蝙蝠Attention:大家肯定已經注意到了,我們線上程函式thread1()、thread2()執行完之前都呼叫了pthread_exit。如果我是呼叫exit()又或者是return會怎樣呢?自己動手試試吧!

pthread_exit()用於執行緒退出,可以指定返回值,以便其他執行緒通過pthread_join()函式獲取該執行緒的返回值。
return是函式返回,只有執行緒函式return,執行緒才會退出。
exit是程序退出,如果線上程函式中呼叫exit,程序中的所有函式都會退出!

“4) 執行緒序1需要線上程2退出後才能退出”第4點也很容易解決,直接在thread1的函式退出之前呼叫pthread_join就OK了。

4、執行緒之間的互斥

上面的程式碼似乎很好的解決了問題的前面4點要求,其實不然!!!因為g_Flag是一個全域性變數,執行緒thread1和thread2可以同時對它進行操作,需要對它進行加鎖保護,thread1和thread2要互斥訪問才行。下面我們就介紹如何加鎖保護——互斥鎖。

互斥鎖:

使用互斥鎖(互斥)可以使執行緒按順序執行。通常,互斥鎖通過確保一次只有一個執行緒執行程式碼的臨界段來同步多個執行緒。互斥鎖還可以保護單執行緒程式碼。

互斥鎖的相關操作函式如下:

#include <pthread.h> 

int pthread_mutex_lock(pthread_mutex_t * mptr); 
int pthread_mutex_unlock(pthread_mutex_t * mptr); 
//Both return: 0 if OK, positive Exxx value on error

在對臨界資源進行操作之前需要pthread_mutex_lock先加鎖,操作完之後pthread_mutex_unlock再解鎖。而且在這之前需要宣告一個pthread_mutex_t型別的變數,用作前面兩個函式的引數。具體程式碼見第5節。

5、執行緒之間的同步

第5點——主執行緒在檢測到g_Flag從1變為2,或者從2變為1的時候退出。就需要用到執行緒同步技術!執行緒同步需要條件變數。

條件變數:

使用條件變數可以以原子方式阻塞執行緒,直到某個特定條件為真為止。條件變數始終與互斥鎖一起使用。對條件的測試是在互斥鎖(互斥)的保護下進行的。

如果條件為假,執行緒通常會基於條件變數阻塞,並以原子方式釋放等待條件變化的互斥鎖。如果另一個執行緒更改了條件,該執行緒可能會向相關的條件變數發出訊號,從而使一個或多個等待的執行緒執行以下操作:

  • 喚醒
  • 再次獲取互斥鎖
  • 重新評估條件

在以下情況下,條件變數可用於在程序之間同步執行緒:

  • 執行緒是在可以寫入的記憶體中分配的
  • 記憶體由協作程序共享

使用條件變數可以以原子方式阻塞執行緒,直到某個特定條件為真為止。”即可用到第5點,主執行緒main函式阻塞於等待g_Flag從1變為2,或者從2變為1。條件變數的相關函式如下:

#include <pthread.h>
 
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); 
int pthread_cond_signal(pthread_cond_t *cptr); 
//Both return: 0 if OK, positive Exxx value on error

pthread_cond_wait用於等待某個特定的條件為真,pthread_cond_signal用於通知阻塞的執行緒某個特定的條件為真了。在呼叫者兩個函式之前需要宣告一個pthread_cond_t型別的變數,用於這兩個函式的引數。

為什麼條件變數始終與互斥鎖一起使用,對條件的測試是在互斥鎖(互斥)的保護下進行的呢?因為“某個特性條件”通常是在多個執行緒之間共享的某個變數。互斥鎖允許這個變數可以在不同的執行緒中設定和檢測。

通常,pthread_cond_wait只是喚醒等待某個條件變數的一個執行緒。如果需要喚醒所有等待某個條件變數的執行緒,需要呼叫:

int pthread_cond_broadcast (pthread_cond_t * cptr);

預設情況下面,阻塞的執行緒會一直等待,知道某個條件變數為真。如果想設定最大的阻塞時間可以呼叫:

int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);

如果時間到了,條件變數還沒有為真,仍然返回,返回值為ETIME。

6、試題最終程式碼

通過前面的介紹,我們可以輕鬆的寫出程式碼了,如下所示:

/*
 是否熟悉POSIX多執行緒程式設計技術?如熟悉,編寫程式完成如下功能:
  1)有一int型全域性變數g_Flag初始值為0;
  2)在主線稱中起動執行緒1,列印“this is thread1”,並將g_Flag設定為1
  3)在主線稱中啟動執行緒2,列印“this is thread2”,並將g_Flag設定為2
  4)執行緒序1需要線上程2退出後才能退出
  5)主執行緒在檢測到g_Flag從1變為2,或者從2變為1的時候退出
   */
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

typedef void* (*fun)(void*);

int g_Flag=0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* thread1(void*);
void* thread2(void*);

/*
 *  when program is started, a single thread is created, called the initial thread or main thread.
 *  Additional threads are created by pthread_create.
 *  So we just need to create two thread in main().
 */

int main(int argc, char** argv)
{
	printf("enter main\n");
	pthread_t tid1, tid2;
	int rc1=0, rc2=0;
	rc2 = pthread_create(&tid2, NULL, thread2, NULL);
	if(rc2 != 0)
		printf("%s: %d\n",__func__, strerror(rc2));

	rc1 = pthread_create(&tid1, NULL, thread1, &tid2);
	if(rc1 != 0)
		printf("%s: %d\n",__func__, strerror(rc1));

	pthread_cond_wait(&cond, &mutex);
	printf("leave main\n");
	exit(0);	
}

/*
 * thread1() will be execute by thread1, after pthread_create()
 * it will set g_Flag = 1;
 */
void* thread1(void* arg)
{
	printf("enter thread1\n");
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	pthread_mutex_lock(&mutex);
	if(g_Flag == 2)
		pthread_cond_signal(&cond);
	g_Flag = 1;
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	pthread_mutex_unlock(&mutex);
	pthread_join(*(pthread_t*)arg, NULL);
	printf("leave thread1\n");
	pthread_exit(0);
}

/*
 * thread2() will be execute by thread2, after pthread_create()
 * it will set g_Flag = 2;
 */
void* thread2(void* arg)
{
	printf("enter thread2\n");
	printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	pthread_mutex_lock(&mutex);
	if(g_Flag == 1)
		pthread_cond_signal(&cond);
	g_Flag = 2;
	printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());
	pthread_mutex_unlock(&mutex);
	printf("leave thread2\n");
	pthread_exit(0);
}

編譯執行可以得到符合要求的結果!

相關推薦

介紹Linux下面執行操作執行同步互斥

 執行緒?為什麼有了程序還需要執行緒呢,他們有什麼區別?使用執行緒有什麼優勢呢?還有多執行緒程式設計的一些細節問題,如執行緒之間怎樣同步、互斥,這些東西將在本文中介紹。我在某QQ群裡見到這樣一道面試題: 是否熟悉POSIX多執行緒程式設計技術?如熟悉,編寫程式完成如下功能

python執行————8執行程序對比

#多程序程式設計 #耗cpu的操作,用多程序程式設計,對於io操作來說,使用多執行緒程式設計,程序切換代價要高於執行緒 import time from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor,as_compl

python執行————3執行間通訊:共享變數,queue

1、共享變數 #通過共享變數 import time import threading url_list = [] def get_detail_html(): global url_list while True: if len(url_list):

java 執行執行併發實戰(生產者消費者模型 1 vs 10) 附案例原始碼

導讀   前二天寫了一篇《Java 多執行緒併發程式設計》點我直達,放國慶,在家閒著沒事,繼續寫剩下的東西,開幹! 執行緒池 為什麼要使用執行緒池   例如web伺服器、資料庫伺服器、檔案伺服器或郵件伺服器之類的。請求的時候,單個任務時間很短,但是請求數量巨大。每一次請求,就會建立一個新執行緒,然後在新執行緒

IOS網路執行shareSDK-使用UIApplaction進行簡單操作

// //  ViewController.swift //  Dome2test // //  Created by 郭文亮 on 2018/11/21. //  Copyright © 2018年 finalliang. All rights rese

PX4概念學習(1)——Linux程序執行基礎

【學習Freeape大神的uORB時,乘機補補有關Linux多程序、多執行緒的知識】 uORB(Micro Object Request Broker,微物件請求代理器)是PX4/Pixhawk系統中非常重要且關鍵的一個模組,它肩負了整個系統的資料傳輸任務,所有的感測器資料

水滴石穿--執行原子操作threadlocalvolatile執行下的單例模式

接著上一篇文章,下面看看幾個比較好理解的知識點!! volatile java關鍵字volatile修飾的變數從字面意義上理解易變的,不穩定的,事實上時告訴編譯器該變數是易變的不要對該變數使用快取等級的優化,每次都從記憶體地址中讀取值。 不過並沒有說明在對volatile修飾的變數進行修

Linux程序執行基礎

【學習Freeape大神的uORB時,乘機補補有關Linux多程序、多執行緒的知識】 uORB(Micro Object Request Broker,微物件請求代理器)是PX4/Pixhawk系統中非常重要且關鍵的一個模組,它肩負了整個系統的資料傳輸任務,所有的感測器資料、GPS、PPM訊

C# 基礎(十四)C#單例模式:首先介紹執行執行加鎖 單例模式。然後介紹單例模式的執行同步執行有序訪問共享記憶體。

一、簡介 本篇文章將介紹如何使用單例模式,也就是類的例項化,在整個專案的生命週期內,只例項化一次。在單例模式中,往往可以看到如SourceCode.cs:這樣的結構的。 SourceCode.cs: public class Singleton { private static

集合IO操作列舉執行

一、集合 /** * lambda過濾 */ @Test public void testLambda(){ Person person1

Java執行(五)執行其他知識簡要介紹

一、執行緒組 [java]  view plain  copy /**   * A thread gr

linux下cpu物理個數執行判斷解析

在Linux體系中,供給了proc檔案體系顯示體系的軟硬體資訊。若是想懂得體系中CPU的供給商和相干裝置資訊,則可以經由過程/proc/cpuinfo檔案獲得。本文章針對該檔案進行簡單的總結。 基於指令集(ISA)的CPU產生的/proc/cpuinfo檔案不一樣,基於X8

併發伺服器的實現(程序執行...)

一、多程序實現併發伺服器 程式碼如下:multiprocess_server.c /* ============================================================================ Name : TCPServ

Java 知識 - 集合執行IOJVM

GitHub 專案地址 Collection Java Collection 新增、刪除等操作時可選操作,如 Arrays.asList,會產生固定大小的集合,會丟擲 UnsupportedOperationException Set HashSet、TreeSet、LinkedH

SimpleDateFormat時間格式化高併發執行時出現問題

SimpleDateFormat是是 Java 中一個非常常用的類,該類用來對日期字串進行解析和格式化輸出,但是DateFormat 和 SimpleDateFormat 類不都是執行緒安全的,在生產環境的多執行緒或高併發情況使用 format() 和 parse() 方法,會出現很多問題:

C#執行基礎(執行的優先順序狀態同步

一、關於多執行緒的優先順序、狀態、同步指令碼如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System

執行實現簡單的socket通訊例項(TCP)

1.伺服器端 import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 類功能描述:先啟動伺服器端,再啟動客戶端 * * @author:*** * @createTime:2018/

DEVOPS-01程序執行程式設計

一、多執行緒程式設計 1.1 forking工作原理 1.1.1 什麼是forking 1. fork(分岔)在Linux系統中使用非常廣泛 2.  當某一命令執行時,父程序(當前程序)fork出一個子程序 3.  父程序將自身資源拷貝一份,命令在子程序中執行時,就具

銀行業務系統(c/s架構socket網路程式設計執行

1、功能要求 包括兩類使用者:管理人員和普通使用者(本文只寫了普通使用者程式) 普通使用者功能:登入登出、存取款、轉賬、查詢餘額 2、技術要求 要求用到多程序多執行緒 要求同時允許多個使用者操作(因為沒有註冊賬號功能,且只初始化了兩個賬號資訊,所以同時只能允許兩個賬號線上)

python執行————7ThreadPoolExecutor執行

所用到的模組:from concurrent.futures import ThreadPoolExecutor,as_completed,wait,FIRST_COMPLETED 1、建立執行緒池:executor = ThreadPoolExecutor(max_workers= ) 2