主頁 > 後端開發 > c++11 執行緒

c++11 執行緒

2020-10-30 22:52:33 後端開發

c++11執行緒基本用法

  • 簡單示例

    1. thread和join()

      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的執行緒執行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的執行緒執行完畢" << endl;
      }
      int main() {
          //thread是標準庫的類,test為可呼叫物件,作為執行緒執行起點
      	thread th(test);
          //join():阻塞主執行緒,讓主執行緒等待子執行緒執行完畢
      	th.join();
      	cout << "主執行緒執行結束" << endl;
      	return 0;
      }
      

      在這里插入圖片描述

    2. detach()

      //傳統多執行緒程式主執行緒要等待子執行緒執行完畢后才能退出
      //detach():將主執行緒和子執行緒分離,可以讓主執行緒不必等待子執行緒
      //一旦detach()之后,與主執行緒關聯的thread物件就會視區和主執行緒的關聯,此時這個子執行緒就會在后臺運行,當子執行緒執行完成后,由語和女性時庫負責清理該執行緒相關資源
      //detach()會讓子執行緒失去我們的控制,使用detach()后不能join()
      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的執行緒執行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的執行緒執行完畢" << endl;
      }
      int main() {
      	thread th(test);
      	th.detach();
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

      在這里插入圖片描述

      主執行緒執行完畢后,子執行緒由運行時庫接管,無法列印出來

    3. joinable()

      //joinable():判斷是否可以成功join()或者detach(),回傳true或者false
      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的執行緒執行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的執行緒執行完畢" << endl;
      }
      int main() {
      	thread th(test);
      	if (th.joinable()) {
      		cout << "1:joinable()==true" << endl;
      	}
      	else {
      		cout << "1:joinable()==false" << endl;
      	}
      	th.detach();
      	if (th.joinable()) {
      		cout << "2:joinable()==true" << endl;
      	}
      	else {
      		cout << "2:joinable()==false" << endl;
      	}
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

    在這里插入圖片描述

  • 其他創建執行緒的方法

    1. 類物件作為可呼叫物件

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class Test {
      public:
      	void operator()() {//不能帶引數
      		cout << "我的執行緒開始執行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "我的執行緒執行完畢" << endl;
      	}
      };
      
      int main() {
      	Test test;
      	thread th(test);
      	th.join();
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

      若把join()改成detach(),主執行緒執行結束,test還在嗎?

      如果物件不在了,執行緒還能繼續執行嗎?

      這個物件實際上是被復制到執行緒中去,執行完執行緒后,test會被銷毀,但是復制的物件還在

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class Test {
      public:
      	Test(){
      		cout << "Test()建構式被執行" << endl;
      	}
      	Test(const Test& test) {
      		cout << "拷貝建構式執行" << endl;
      	}
      	~Test() {
      		cout << "解構式執行" << endl;
      	}
      	void operator()() {//不能帶引數
      		cout << "執行緒開始執行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "執行緒執行完畢" << endl;
      	}
      };
      
      int main() {
      	Test test;
      	thread th(test);
      	th.detach();
      	for (int i = 0; i < 10; ++i) {
      		cout << "主執行緒執行完畢" << i << endl;
      	}
      	return 0;
      }
      

      在這里插入圖片描述

      將detach()改為join()后

    在這里插入圖片描述

    1. 用lambda運算式

      #include<iostream>
      #include<thread>
      using namespace std;
      
      
      int main() {
      	auto test = [] {
      		cout << "執行緒開始執行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "執行緒執行完畢" << endl;
      	};
      	thread th(test);
      	th.join();
      	cout << "主執行緒執行完畢"  << endl;
      	return 0;
      }
      
  • 傳遞臨時物件作為執行緒引數

    1. 可呼叫物件帶有引數時

      #include<iostream>
      #include<thread>
      using namespace std;
      
      //傳入的不是myNum的參考,指向地址不同,實際是值傳遞,即便主執行緒detach(),子執行緒使用num不會出問題
      //傳入的指標依舊是指向muBuf的地址,所以detach后,子執行緒會出問題,可以將char*改為const string&
      //使用const string&也有問題,即:不知道mybuf不知道什么時候轉換為string,如果主執行緒執行完畢后,還沒轉換完畢,就有問題
      void test(const int& num, char* buf) {
      	cout << "執行緒開始執行" << endl;
      	cout << num << endl;
      	cout << buf << endl;
      	cout << "執行緒執行完畢" << endl;
      }
      
      int main() {
      	int myNum = 1;
      	int& num = myNum;
      	char myBuf[] = "only for test!";
      	thread th(test, num, myBuf);
      	th.join();
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      
      // 生成一個臨時string物件,系結const string&
      //使用這個方法,就會讓string物件在主執行緒執行完畢前構造
      //在創建執行緒的同時構造臨時物件是可行的
      thread th(test,num,string(myBuf));
      

      總結(針對detach())

      (1)若傳遞int這種簡單型別引數,建議都是值傳遞,不要用參考

      (2)如果傳遞類物件,避免隱式型別轉換,全部都要在創建執行緒就構建臨時物件,然后函式引數中用參考,否則系統還會構造一次

    2. 執行緒id的概念

      (1)每個執行緒(包括主執行緒)實際都對應著一個數字,并且數字都不同

      (2)執行緒id可以用c++標準庫中的函式來獲取,即:std::this_thread::get_id()

    3. 臨時物件構造時機抓捕

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class A {
      public:
      	int num;
      	//型別轉換建構式,可以把一個int轉換成一個類A物件
      	A(int num) :num(num) {
      		cout << "建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	A(const A& a) :num(a.num) {
      		cout << "拷貝建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	~A() {
      		cout << "解構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      };
      
      void test(const A& a) {
      	cout << "執行緒test的引數地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主執行緒id是" << std::this_thread::get_id() << endl;
      	int num = 2;
      	thread th(test, num);
      	th.join();
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

      在這里插入圖片描述

      A類物件在子執行緒中構造的,若改為detach()后,當主執行緒執行完畢后,還沒構造,就出現問題了

      //在創建執行緒就構建臨時物件
      thread th(test, A(num));
      

    在這里插入圖片描述

  • 傳遞類物件、智能指標作為執行緒引數

    1. 執行緒引數為應用時修改值,不會影響主執行緒的值

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class A {
      public:
      	mutable int num;//標注為const也能修改
      	//型別轉換建構式,可以把一個int轉換成一個類A物件
      	A(int num) :num(num) {
      		cout << "建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	A(const A& a) :num(a.num) {
      		cout << "拷貝建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	~A() {
      		cout << "解構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      };
      
      void test(const A& a) {
      	a.num = 199;//修改不會影響main的值
      	cout << "a.num=" << a.num << endl;
      	cout << "執行緒test的引數地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主執行緒id是" << std::this_thread::get_id() << endl;
      	A a(10);
      	thread th(test, a);//將類物件作為執行緒引數
      	th.join();
      	cout << "主執行緒的a.num=" << a.num << endl;
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

    在這里插入圖片描述

    1. std::ref函式,可以讓執行緒傳入的引數不被復制(注:沒使用detach())

      //將執行緒傳入引數加上std::ref
      thread th(test, std::ref(a));
      

    在這里插入圖片描述

    1. 傳遞智能指標

      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test(unique_ptr<int> uptr) {
      	cout << "子執行緒id是" << std::this_thread::get_id() << endl;
      	cout << "當前智能指標的地址是" << uptr << endl;
      }
      
      int main() {
      	cout << "主執行緒id是" << std::this_thread::get_id() << endl;
      	unique_ptr<int> uptr(new int(10));
      	cout << "當前智能指標的地址為" << uptr << endl;
      	thread th(test, std::move(uptr));//將類物件作為執行緒引數
      	th.join();
      	cout << "主執行緒執行完畢" << endl;
      	return 0;
      }
      

    在這里插入圖片描述

    若改為detach(),當主執行緒執行完畢后,uptr被釋放,但子執行緒還在執行,此時就會出現問題

  • 用成員函式指標做執行緒函式

    #include<iostream>
    #include<thread>
    using namespace std;
    
    class A {
    public:
    	mutable int num;
    	//型別轉換建構式,可以把一個int轉換成一個類A物件
    	A(int num) :num(num) {
    		cout << "建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	A(const A& a) :num(a.num) {
    		cout << "拷貝建構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	~A() {
    		cout << "解構式執行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	void thread_work(int num) {
    		cout << "子執行緒thread_work執行了" << ",threadid=" << std::this_thread::get_id() << endl;
    	}
    };
    
    void test(const A& a) {
    	a.num = 199;//修改不會影響main的值
    	cout << "a.num=" << a.num << endl;
    	cout << "執行緒test的引數地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
    }
    
    int main() {
    	cout << "主執行緒id是" << std::this_thread::get_id() << endl;
    	A a(10);
        //第一個引數是物件函式指標,第二個引數物件,其后引數為函式所需引數
    	thread th(&A::thread_work, a, 1);
    	th.join();
    	cout << "主執行緒的a.num=" << a.num << endl;
    	cout << "主執行緒執行完畢" << endl;
    	return 0;
    }
    

c++11執行緒的互斥量

  • 創建和等待多個執行緒

    #include<iostream>
    #include<thread>
    #include<vector>
    using namespace std;
    
    
    //執行緒的入口函式
    void test(int num) {
    	cout << "執行緒開始執行,執行緒編號=" << num << endl;
    	for (int i = 0; i < 10; ++i);
    	cout << "執行緒執行結束了,執行緒編號=" << num << endl;
    }
    
    int main() {
    	cout << "主執行緒開始執行" << endl;
    	//創建和等待多個執行緒
    	vector<thread> threads;
    	//創建10個執行緒,執行緒入口統一使用test
    	for (int i = 0; i < 10; ++i) {
    		threads.push_back(thread(test, i));//創建10個執行緒,同時這10個執行緒開始執行
    	}
    	for (auto i = threads.begin(); i != threads.end(); ++i) {
    		i->join();//等待10個執行緒回傳
    	}
    	cout << "主執行緒執行結束" << endl;
    	return 0;
    }
    

    在這里插入圖片描述

    表明執行緒的執行是無序的,和作業系統內部對執行緒的調度有關

  • 資料共享問題分析——mutex

    1. 只讀的資料

      #include<iostream>
      #include<thread>
      #include<vector>
      using namespace std;
      
      int global = 10;//共享資料
      
      //執行緒的入口函式
      void test(int num) {
      	cout << "編號為" << std::this_thread::get_id() << "列印global_v的值" << global << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "執行緒執行結束了,執行緒編號=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主執行緒開始執行" << endl;
      	//創建和等待多個執行緒
      	vector<thread> threads;
      	//創建10個執行緒,執行緒入口統一使用test
      	for (int i = 0; i < 10; ++i) {
      		threads.push_back(thread(test, i));//創建10個執行緒,同時這10個執行緒開始執行
      	}
      	for (auto i = threads.begin(); i != threads.end(); ++i) {
      		i->join();//等待10個執行緒回傳
      	}
      	cout << "主執行緒執行結束" << endl;
      	return 0;
      }
      

    在這里插入圖片描述

    所有執行緒讀取的資料都相等,即:只讀資料安全穩定

    1. 有讀有寫

      #include<iostream>
      #include<thread>
      using namespace std;
      
      int num[10] = { 0 };
      
      void write() {
      	for (int i = 0; i < 10; ++i) {
      		num[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      }
      
      void read() {
      	for (int i = 1; i < 10; ++i) {
      		if (num[i] != num[i - 1]) {
      			cout << "資料不一致" << endl;
      		}
      	}
      }
      
      int main() {
      	cout << "主執行緒開始執行" << endl;
      	//創建2個執行緒,一個執行緒負責讀,一個負責寫
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主執行緒執行結束" << endl;
      	return 0;
      }
      

      在這里插入圖片描述

  • 共享資料的保護

    1. 互斥量的基本概念

      類的物件,可以理解為一把鎖,多個執行緒嘗試用lock()成員函式來加鎖這把鎖頭,只有一個執行緒可以鎖定成功(成功的標志是lock()函式回傳),如果沒有鎖定成功,執行緒就會阻塞在這里,不斷嘗試加鎖

    2. 互斥量的用法

      1. 先lock(),操作共享資料,unlock()

      2. lock()和unlock()要成對使用,lock()必然要有unlock,每呼叫一次lock(),必然要呼叫一次unlock()

      3. 注意:mutex是不可復制物件,所以mutex作為類的成員要注意,C++中thread呼叫“帶mutex的類”的成員函式報錯C2661:std::tuple解決方法_tomwillow的博客-CSDN博客

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      int num[10] = { 0 };
      
      void write() {
      	int copy[10];
      	for (int i = 0; i < 10; ++i) {
      		copy[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      	g_mutex.lock();
      	memcpy(num, copy, 10);
      	g_mutex.unlock();
      }
      
      void read() {
      	int copy[10] = { 0 };
      	g_mutex.lock();
      	memcpy(copy, num, 10);
      	g_mutex.unlock();
      	for (int i = 1; i < 10; ++i) {
      		if (copy[i] != copy[i - 1]) {
      			cout << "資料不一致" << endl;
      		}
      	}
      	
      }
      
      int main() {
      	cout << "主執行緒開始執行" << endl;
      	//創建2個執行緒,一個執行緒負責讀,一個負責寫
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主執行緒執行結束" << endl;
      	return 0;
      }
      

    在這里插入圖片描述

    1. 為了防止忘記unlock(),引入一個叫std::lock_guard的類模板,類似于智能指標

      (1)std::lock_guard直接取代了lock()和unlock()

      (2)lock_guard建構式中執行了lock(),解構式中執行了unlock()

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      int num[10] = { 0 };
      
      void write() {
      	int copy[10];
      	for (int i = 0; i < 10; ++i) {
      		copy[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      	std::lock_guard<mutex> write_mutex(g_mutex);
      	memcpy(num, copy, 10);
      }
      
      void read() {
      	int copy[10] = { 0 };
      	std::lock_guard<mutex> read_mutex(g_mutex);
      	memcpy(copy, num, 10);
      	for (int i = 1; i < 10; ++i) {
      		if (copy[i] != copy[i - 1]) {
      			cout << "資料不一致" << endl;
      		}
      	}
      	
      }
      
      int main() {
      	cout << "主執行緒開始執行" << endl;
      	//創建2個執行緒,一個執行緒負責讀,一個負責寫
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主執行緒執行結束" << endl;
      	return 0;
      }
      

      如果不想執行緒執行完畢后std::lock_guard才執行解構式,可以將其和需要加鎖的共享資料放到{}中

      {
          std::lock_guard<mutex> write_mutex(g_mutex);
          memcpy(num, copy, 10);
      }
      //此時std::lock_guard屬于{}這個作用域,當在{}外后,std::lock_guard生命周期結束,就會析構,即執行unlock
      
    2. 死鎖

      1. 死鎖的前提是至少有兩個互斥量(鎖頭)才能產生

      2. 目前有兩個互斥量,即:鎖1,鎖2,兩個執行緒A,B

        (1)執行緒A執行的時候,這個執行緒先鎖鎖1,把鎖1鎖成功了,然后它去鎖鎖2,此時出現背景關系切換,執行緒A讓出CPU

        (2)執行緒B執行了,這個執行緒先鎖鎖2,因為鎖2沒有被鎖,所以鎖2鎖成功,然后它去鎖鎖1

        (3)此時死鎖產生,執行緒A拿不到鎖2,解不開鎖1,執行緒B拿不到鎖1,解不開鎖2,兩個執行緒就無限互相等待下去

      3. 死鎖的一般解決辦法:保證兩個互斥量的lock()順序一致

      4. std::lock()函式模板

        1. 一次鎖住兩個或兩個以上的互斥量(1個不行),它就不會存在多個執行緒因為鎖的順序導致的死鎖風險,

        2. 如果互斥量中有一個沒鎖住,它就會一直等待,直到所有的互斥量都鎖住

        3. 要么多個互斥量都鎖住,要么都沒有鎖住,如果只鎖一個其他沒成功,它就會立即把已經lock()的都unlock()

          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          g_mutex1.unlock();
          g_mutex2.unlock();
          
        4. std::lock_guard的std::adopt_lock

          //是一個結構體物件,其一個標記作用,表示互斥量已經lock()
          //std::adopt_lock讓lock_guard不執行lock()
          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          std::lock_guard<mutex> guard1(g_mutex1,std::adopt_lock);
          std::lock_guard<mutex> guard2(g_mutex2,std::adopt_lock);
          
      5. unique_lock

        (1)unique_lock是個類模板,和lock_guard類似,都是對mutex進行lock()和unlock()操作的

        (2)unique_lock比lock_guard更靈活,但效率上差一點,記憶體占用多一點,

        (3)用法和lock_guard類似

        mutex g_mutex;
        std::unique_lock<std::mutex> uniqueLock(g_mutex);
        

        (4)unique_lock第二個引數

        ? a. std::adopt_lock(lock_guard也可使用):表示互斥量已經lock(),即:不需要在unique_lock的建構式進行lock()

        ? b. std::try_to_lock:會嘗試mutex的lock()去鎖定mutex,但如果沒有鎖定成功,就會立即回傳,并不會阻塞(前提是使用try_to_lock前不能單獨使用lock())

        std::unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
        if(uniqueLock.owns_lock()){//嘗試Lock()成功
            
        }
        
        
        //實體
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	this_thread::sleep_for(chrono::milliseconds(2000));
        	memcpy(num, copy, 10);
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
        	if (uniqueLock.owns_lock()) {
        		memcpy(copy, num, 10);
        	}
        	else {
        		cout << "執行緒2沒有拿到鎖" << endl;
        	}
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "資料不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主執行緒開始執行" << endl;
        	//創建2個執行緒,一個執行緒負責讀,一個負責寫
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主執行緒執行結束" << endl;
        	return 0;
        }
        

        在這里插入圖片描述

        ? c. std::defer_lock:并沒有給mutex,即:初始化一個沒有lock()的mutex(defer_lock的前提是不能先lock()),經常配合unique_lock的成員函式使用

      6. unique_lock的重要成員函式

        (1)lock()

        (2)unlock():unique_lock也可以自動解鎖

        (3)try_lock():(類似于try_to_lock)嘗試給互斥量加鎖,如果拿不到鎖,則回傳false,如果拿到了鎖,回傳true,這個函式是不阻塞

        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	this_thread::sleep_for(chrono::milliseconds(2));
        	memcpy(num, copy, 10);
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex,defer_lock);
        	if (uniqueLock.try_lock() == true) {
        		memcpy(copy, num, 10);
        	}
        	else {
        		cout << "執行緒2沒有拿到鎖" << endl;
        	}
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "資料不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主執行緒開始執行" << endl;
        	//創建2個執行緒,一個執行緒負責讀,一個負責寫
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主執行緒執行結束" << endl;
        	return 0;
        }
        

        在這里插入圖片描述

        (4)release():回傳它所管理的mutex物件指標,并釋放所有權,也就是說,這個unique_lock和mutex不再聯系(unlock不會釋放所有權),

        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	mutex* ptx = uniqueLock.release();//釋放mutex所有權,所以需要自己unlock()
        	memcpy(num, copy, 10);
        	ptx->unlock();
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex);
        	memcpy(copy, num, 10);
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "資料不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主執行緒開始執行" << endl;
        	//創建2個執行緒,一個執行緒負責讀,一個負責寫
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主執行緒執行結束" << endl;
        	return 0;
        }
        
      7. unique_lock所有權

        mutex g_mutex;
        unique_lock<mutex> uniqueLock(g_mutex);//uniqueLock擁有g_mutex的所有權
        //不能復制所有權,但是所有權可以轉移
        
        //轉移方法1
        unique_lock<mutex> uniqueLock_(move(uniqueLock));//當前uniqueLock失去g_mutex控制權,指向空,uniqueLock_指向g_mutex
        
        //轉移方法2
        //從函式回傳一個區域的unique_lock物件是允許的
        //回傳這種區域物件會導致系統生成臨時的unique_lock物件,并呼叫unique_lock的移動建構式
        unique_lock<mutex> rtn_unique_lock(){
            unique_lock<mutex> uniqueLock(g_mutex);
            return uniqueLock;
        }
        
  • 單例設計模式共享資料分析、解決,call_once

    1. 單例設計模式

      (1)單例:整個專案中,有某個或者某些特殊的類,并且只能創建一個屬于該類的物件

      (2)單例類示例

      #include<iostream>
      using namespace std;
      
      class MyCAS {//這是一個單例類
      private:
      	MyCAS(){}//私有化建構式
      private:
      	static MyCAS* m_instance;//靜態成員變數
      public:
      	static MyCAS* getInstance() {
      		if (m_instance == nullptr) {
      			m_instance = new MyCAS();
      			static Release release;//當程式退出時執行解構式
      		}
      		return m_instance;
      	}
      	class Release {//用來釋放物件
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      };
      //類靜態變數初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      int main() {
      	MyCAS* ptr = MyCAS::getInstance();//創建一個物件,回傳該類(物件)指標
      	MyCAS* ptr_ = MyCAS::getInstance();
      	cout << "ptr=" << ptr << endl;
      	cout << "ptr_=" << ptr_ << endl;
      	return 0;
      }
      

    在這里插入圖片描述

    1. 單例設計模式共享資料問題分析、解決

      問題:在創建的執行緒(非主執行緒)中創建單例類物件,并且這種執行緒可能不止一個,就需要將getInstance()這種成員函式互斥

      原因:當第一個執行緒在new之前失去cpu,第二個執行緒就會執行new一次,此時第一個執行緒再次執行,就又會new一次,這就不符合單例設計模式了

      示例:

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      
      class MyCAS {//這是一個單例類
      private:
      	MyCAS(){}//私有化建構式
      private:
      	static MyCAS* m_instance;//靜態成員變數
      public:
      	static MyCAS* getInstance() {
      		//提高效率,只有第一次沒初始化時會加鎖
      		if (m_instance == nullptr) {//雙重鎖定(雙重檢查)
      			unique_lock<mutex> lock(g_mutex);//自動加鎖
      			if (m_instance == nullptr) {
      				m_instance = new MyCAS();
      				static Release release;//當程式退出時執行解構式
      			}
      		}
      		return m_instance;
      	}
      
      	class Release {//用來釋放物件
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      
      	void fun() {
      		cout << "測驗" << endl;
      	}
      };
      //類靜態變數初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      void thread1() {
      	cout << "執行緒th1開始執行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "執行緒th1執行完畢" << endl;
      }
      
      void thread2() {
      	cout << "執行緒th2開始執行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "執行緒th2執行完畢" << endl;
      }
      
      int main() {
      	thread th1(thread1);
      	thread th2(thread2);
      	th1.join();
      	th2.join();
      	return 0;
      }
      
    2. std::call_once()——c++11標準

      函式的第二個引數是一個函式名,需要和std::once_flag標記結合使用

      功能:保證函式只被呼叫一次,即:通過std::once_flag這個標記來判斷函式是否執行,當呼叫call_once()成功后,call_once()就把這個標記設定為一種已呼叫狀態,后續再次呼叫call_once(),只要once_falg被設定為已呼叫,就不會執行了

      具備互斥量的能力

      #include<iostream>
      #include<thread>
      #include <mutex>
      using namespace std;
      
      once_flag g_flag;//這是系統定義的標記
      
      class MyCAS {//這是一個單例類
      	static void CreateInstance() {//只被呼叫一次
      		m_instance = new MyCAS();
      		static Release release;//當程式退出時執行解構式
      	}
      private:
      	MyCAS(){}//私有化建構式
      private:
      	static MyCAS* m_instance;//靜態成員變數
      public:
      	static MyCAS* getInstance() {
      		//兩個執行緒同時執行到這里,其中一個執行緒要等另外一個執行緒執行完畢CreateInstance后,就會放棄執行CreateInstance
      		call_once(g_flag, CreateInstance);
      		return m_instance;
      	}
      
      	class Release {//用來釋放物件
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      
      	void fun() {
      		cout << "測驗" << endl;
      	}
      };
      //類靜態變數初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      void thread1() {
      	cout << "執行緒th1開始執行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "執行緒th1執行完畢" << endl;
      }
      
      void thread2() {
      	cout << "執行緒th2開始執行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "執行緒th2執行完畢" << endl;
      }
      
      int main() {
      	thread th1(thread1);
      	thread th2(thread2);
      	th1.join();
      	th2.join();
      	return 0;
      }
      

      c++11執行緒的條件變數

  • 輪詢機制:每隔一定時間,進行查詢

    缺點:查詢不能太頻繁(浪費CPU),也不能太不頻繁(緩沖區滿),難以把握,性能不佳

    #include <iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    
    mutex g_mutex;
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息時間在50-1000毫秒之間
            //存放一個物品(這里存放的資料代表物品)
            g_mutex.lock();
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_mutex.unlock();
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));
            g_mutex.lock();
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            g_mutex.unlock();
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    

    在這里插入圖片描述

  • 條件變數——std::condition_variable

    (1)std::condition_variable實際是一個類,是一個和條件相關的一個類,即:等待一個條件達成,

    (2)這個類是需要和互斥量來配合作業,用的時候我們要生成這個類的物件

    (3)wait()

    ? a. 如果第二個引數(可呼叫物件)回傳的值是false,那么wait()將解鎖互斥量,并堵塞到 本行,堵塞到其他某個執行緒呼叫notify_one()成員函式為止,如果第二個引數回傳值是true,那么wait()會直接回傳

    ? b. 如果wait()沒有第二個引數,那么就和第二個引數回傳false效果一樣,

    (4)notify_one()

    ? (1)嘗試把一個wait()的執行緒喚醒,如果沒有wait()執行緒,那么notify_one()就沒效果,但是不會阻塞在notify_one()這里

    ? (2)當其他執行緒用notify_one()將wait()【原本阻塞】,wait()就開始恢復干活了,即

    ? a. 不斷嘗試重新獲取互斥量鎖,如果獲取不到,執行緒就阻塞在wait()這里等著獲取鎖, b. 如果獲取到就繼續執行;獲取到鎖就執行lock(),

    ? 如果wait有第二個引數,就判斷這個引數回傳值,如果回傳值為false,wait()又會對互斥量解鎖,并再次阻塞,等待notify_one()喚醒

    ? 如果wait沒有第二個引數,則執行緒繼續執行

    (5)示例

    #include <iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    
    mutex g_mutex;
    condition_variable g_cond;//生成一個條件變數物件
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            this_thread::sleep_for(chrono::milliseconds(50 * r));//避免本執行緒notify_one()后比wait()先拿到鎖,休息時間在50-1000毫秒之間
            //存放一個物品(這里存放的資料代表物品)
            unique_lock<mutex> lock(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_cond.notify_one();
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            unique_lock<mutex> lock(g_mutex);
            g_cond.wait(lock, [&] {//lambda為可呼叫物件
                if (g_count > 0)
                    return true;
                return false;
                });
            for (int i = 0; i < g_count; ++i) {
                cout << "消耗物品:" << g_buf[i] << endl;
            }
            g_count = 0;
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • 上述代碼深入思考

    (1) 當洗掉this_thread::sleep_for(chrono::milliseconds(50 * r))后,放入和消耗就可能不會交替執行,因為notify_one()后,執行緒可能先于wait()拿到鎖

在這里插入圖片描述

(2)注意:notify_one()不一定起作用,但不會阻塞,因為執行notify_one()時,如果沒有其他執行緒wait(),它就沒有效果

  • notify_all()

    當程式中有多個執行緒使用wait()時,使用notify_one()只會在某時刻喚醒其中任意一個執行緒,另外其他執行緒依然在阻塞中,即:任意時刻只有一個wait()嘗試拿鎖,其他都在阻塞中

    使用notify_all()會將所有wait()的執行緒都喚醒,所有執行緒的wait()都會嘗試拿鎖

c++11 async、future、packaged_task、promise

  • std::asyc、std::future創建后臺任務并回傳值

    1. std::asyc:是一個函式模板,用來啟動一個異步任務,啟動一個異步任務后,它會回傳一個std::future(類模板)物件

    2. 啟動一個異步任務:就是自動創建一個執行緒并開始執行對應的執行緒入口函式,它回傳一個std::future物件

    3. std::future物件里就含有執行緒入口函式回傳的結果(執行緒回傳的結果),可以通過呼叫future物件的成員函式get()來獲取結果

    4. std::future:提供了一種訪問異步操作結果的機制,即:這個結果可能無法馬上到達,但是不久的將來,當執行緒執行完畢的時候,就可以拿到結果

    5. 示例

      #include<iostream>
      #include<future>
      using namespace std;
      
      int myThread() {//執行緒入口函式
      	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      	this_thread::sleep_for(chrono::milliseconds(5000));
      	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      	return 5;
      }
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	future<int> result = async(myThread);//創建一個執行緒并執行,但是主函式不會阻塞在這里,會繼續向下執行
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;//主函式會阻塞在這里,等待執行緒回傳
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include<future>
      using namespace std;
      
      class Thread {
      public:
      	int myThread(int num) {//執行緒入口函式
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      	}
      };
      
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	Thread th;
      	future<int> result = async(&Thread::myThread,&th,12);//創建一個執行緒并執行,但是主函式不會阻塞在這里,會繼續向下執行
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;//主函式會阻塞在這里,等待執行緒回傳
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
    6. 上述程式通過future物件的get()成員函式等待執行緒結束并回傳結果,即:會阻塞future物件所在的執行緒,并回傳結果

    7. future物件的get()成員函式只能呼叫一次

    8. future物件還有一個wait()成員函式,該函式只是等待執行緒結束,本身不會回傳結果

    9. 額外向std::async()傳遞一個引數,該引數型別是std::launch型別(列舉型別),來達到一些特殊的目的

      (1)std::launch::deferred:不會創建新執行緒,通過future物件呼叫get()或者wait()函式,就會直接呼叫async中的可呼叫物件

      #include<iostream>
      #include<future>
      using namespace std;
      
      class Thread {
      public:
      	int myThread(int num) {//執行緒入口函式
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      	}
      };
      
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	Thread th;
      	future<int> result = async(launch::deferred, &Thread::myThread, &th, 12);
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      

      在這里插入圖片描述

      (2)std::launch::async:強制這個異步任務在新執行緒上執行,即:系統必須創建出新執行緒執行可呼叫物件

      (3)std::launch::async | std::launch::deferred:這是async的默認值,表示呼叫async的行為可能是創建新執行緒并立即執行,或者沒有創建新執行緒并延遲到呼叫get()或者wait()才開始執行任務入口函式

    10. std::async和std::thread的區別:

      (1)std::async不一定會創建新執行緒執行可呼叫物件,std::thread會創建新執行緒,如果系統資源緊張,創建執行緒失敗,那么整個程式就會崩潰

      (2)std::async呼叫方法可以用簡單的get()函式拿到執行緒可呼叫物件的回傳值

      (3)std::thread創建的執行緒過多,可能創建失敗,系統報告例外,崩潰,而std::async一般不會報告例外、崩潰,因為當系統資源緊張導致無法創建新執行緒的時候,std::async使用默認值呼叫時就不會創建新執行緒,而是后序當某個執行緒使用get()獲取回傳值時,就在該執行緒執行可呼叫物件

    11. std::async使用默認值

      當std::async使用默認值作為引數時,會產生不確定性【即:是否創建新執行緒】

      借助future的wait_for()來判斷是否創建新執行緒

      #include<iostream>
      #include<thread>
      #include<atomic>
      #include<future>
      using namespace std;
      
      atomic<int> g_num = 0;
      
      int myThread() {
      	cout << "myThread() start, " << "thread_id=" << this_thread::get_id() << endl;
      	cout << "myThread() end, " << "thread_id=" << this_thread::get_id() << endl;
      	return 1;
      }
      
      int main() {
      	cout << "main() start, " << "thread_id=" << this_thread::get_id() << endl;
      	future<int> result = async(myThread);
      	future_status status = result.wait_for(0s);//等待chrono::seconds(0)
      	if (status == future_status::deferred) {
      		cout << "沒有創建新執行緒" << endl;
      		cout << result.get() << endl;//此時才執行myThread()
      	}
      	else {
      		//創建了新執行緒
      		if (status == future_status::ready) {
      			cout << "執行緒成功創建" << endl;
      			cout << result.get() << endl;
      		}
      		else if (status == future_status::timeout) {
      			//超時,執行緒還沒執行完畢
      			cout << "超時,執行緒還在執行中" << endl;
      			cout << result.get() << endl;
      		}
      	}
      	cout << "main() end, " << "thread_id=" << this_thread::get_id() << endl;
      	return 0;
      }
      
  • std::packaged_task

    1. std::packaged_task:是一個類模板,模板引數是各種可呼叫物件,通過std::packaged_task把各種可呼叫物件包裝起來,方便將來作為執行緒入口函式

    2. packaged_task:包裝的物件還是可以直接呼叫的

    3. 可以通過get_future()獲取future物件,從而取得執行緒的回傳值

    4. 示例

      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int myThread(int num) {//執行緒入口函式
      	cout << num << endl;
      	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      	this_thread::sleep_for(chrono::milliseconds(5000));
      	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      	return 5;
      }
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc(myThread);//將函式myThread通過packaged_task包裝起來
      	thread th(std::ref(myFunc), 1);
      	th.join();
      	std::future<int> result = myFunc.get_future();
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      			cout << num << endl;
      			cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      			this_thread::sleep_for(chrono::milliseconds(5000));
      			cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      			return 5;
      		});
      	thread th(ref(myFunc), 12);
      	th.join();
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      			cout << num << endl;
      			cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      			this_thread::sleep_for(chrono::milliseconds(5000));
      			cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      			return 5;
      		});
      	myFunc(12);//直接呼叫
      	future<int> result = myFunc.get_future();
      	cout << result.get() << endl;//注意,沒有創建新執行緒,而是觸發lambda運算式執行,即:相當于函式呼叫
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      #include<vector>
      using namespace std;
      
      vector<packaged_task<int(int)>> vec;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      		});
      	vec.push_back(move(myFunc));//這里用到了移動語意,此時myFunc為空
      	packaged_task<int(int)> myFunc_ = move(vec.back());
      	vec.pop_back();
      	myFunc_(123);
      	future<int> result = myFunc_.get_future();
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
  • std::promise

    std::promise也是一個類模板,能夠在某個執行緒中給它賦值,然后可以在其他執行緒中,將這個值取出使用

    #include<iostream>
    #include<thread>
    #include <future>
    using namespace std;
    
    void myThread(promise<int>& temp, int num) {
    	//模擬完成一系列復雜操作
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	temp.set_value(num);//將結果保存到promise物件中
    }
    
    int main() {
    	promise<int> myTemp;//宣告一個promise物件,保存型別為int
    	thread th(myThread, ref(myTemp), 520);
    	th.join();
    	//獲取結果值
    	future<int> result = myTemp.get_future();//promise和future系結,用于獲取執行緒回傳值
    	cout << result.get() << endl;
    	return 0;
    }
    
    #include<iostream>
    #include<thread>
    #include <future>
    using namespace std;
    
    void myThread(promise<int>& temp, int num) {
    	//模擬完成一系列復雜操作
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	temp.set_value(num);//將結果保存到promise物件中
    }
    
    void myThread_(future<int>& temp) {
    	int result = temp.get();
    	cout << "myThread_ result=" << result << endl;
    }
    
    int main() {
    	promise<int> myTemp;//宣告一個promise物件,保存型別為int
    	thread th(myThread, ref(myTemp), 520);
    	th.join();
    	future<int> result = myTemp.get_future();//promise和future系結,用于獲取執行緒回傳值
    	thread th_(myThread_, ref(result));
    	th_.join();
    	return 0;
    }
    

future其他成員函式、shared_future、atomic

  • std::future其它成員函式

    wait_for(time):阻塞time時長,若time時長后,執行緒還沒回傳,則wait_for回傳future_status::timeout,如果在time時長之內,執行緒成功回傳,則wait_for()回傳future_status::ready,如果async第一個引數設定為std::launch::deferred,則wait_for()回傳future_status::deferred

    #include<iostream>
    #include<future>
    using namespace std;
    
    int myThread() {//執行緒入口函式
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	this_thread::sleep_for(chrono::milliseconds(1000));
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    	return 5;
    }
    
    int main() {
    	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
    	future<int> result = async(myThread);//創建一個執行緒并執行,但是主函式不會阻塞在這里,會繼續向下執行
    	cout << "continue……!" << endl;
    	//列舉型別
    	future_status status = result.wait_for(chrono::seconds(2));
    	if (status == future_status::timeout) {
    		cout << "timeout" << endl;
    	}
    	else if (status == future_status::ready) {
    		cout << "成功回傳" << endl;
    		cout << result.get() << endl;
    	}
    	else if (status == future_status::deferred) {
    		cout << "deferred" << endl;
    		cout << result.get() << endl;
    	}
    	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
    	return 0;
    }
    
    

在這里插入圖片描述

  • std::shared_future

    std::future只能呼叫一次get()函式,因為get()函式的設計是一個移動語意

    std::shared_future:也是一個類模板,get()函式是復制資料

    #include<iostream>
    #include <future>
    #include<thread>
    using namespace std;
    
    int myThread() {//執行緒入口函式
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    	return 5;
    }
    
    void myThread_(shared_future<int>& temp) {
    	cout << "myThread_() start," << "threadid=" << this_thread::get_id() << endl;
    	cout << "myThread_():" << temp.get() << endl;
    	cout << "myThread_() end," << "threadid=" << this_thread::get_id() << endl;
    }
    
    int main() {
    	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
    	packaged_task<int(void)> myFunc(myThread);//將函式myThread通過packaged_task包裝起來
    	thread th(std::ref(myFunc));
    	th.join();
    	shared_future<int> result = myFunc.get_future();
    	cout << "main():"<< result.get() << endl;
    	thread th_(myThread_, std::ref(result));
    	th_.join();
    	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
    	return 0;
    }
    

    在這里插入圖片描述

  • std::atomic——原子操作

    原子操作概念及其范例

    (1)原子操作:是指“不可分割的操作”,要么執行成功,要么失敗,即:是在多執行緒中不會打斷的程式執行片段

    (2)原子操作:比互斥量的效率更勝一籌

    (3)互斥量加鎖一般針對的是一個代碼片段,而原子操作針對的一般都是一個變數,而不是一個代碼片段

    (4)std::atomic:類模板,用來代表原子操作

    (5)示例

    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<int> g_num = 0;
    
    void Write() {
    	for (int i = 0; i < 1000000; ++i) {
            //可替換成g_num+=1;
    		++g_num;//操作為原子操作,在多執行緒中不會被打斷
    	}
    }
    
    int main() {
    	thread th1(Write);
    	thread th2(Write);
    	th1.join();
    	th2.join();
    	cout<<"兩個執行緒執行完畢,最終g_num="<<g_num<<endl;
    	return 0;
    }
    
    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<bool> g_flag = false;//執行緒退出標記
    
    void myThread() {
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	while (!g_flag) {
    		cout << "myThread() run," << "threadid=" << this_thread::get_id() << endl;
    		this_thread::sleep_for(chrono::milliseconds(1000));
    	}
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    }
    
    int main() {
    	thread th1(myThread);
    	thread th2(myThread);
    	this_thread::sleep_for(chrono::milliseconds(2000));
    	g_flag = true;
    	th1.join();
    	th2.join();
    	return 0;
    }
    

    (6)atomic原子操作,針對++,--,+=,-=,*=,&=,!=是支持的,其它可能不支持

    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<int> g_num = 0;
    
    void Write() {
    	for (int i = 0; i < 1000000; ++i) {
    		g_num = g_num + 1;
    	}
    }
    
    int main() {
    	thread th1(Write);
    	thread th2(Write);
    	th1.join();
    	th2.join();
    	cout << "兩個執行緒執行完畢,最終g_num=" << g_num << endl;
    	return 0;
    }
    

    在這里插入圖片描述

    (7)load()函式:以原子方式讀取atomic物件的值

    atomic<int> atm1;
    atomic<int> atm2=atm1;//嘗試參考已洗掉的函式,拷貝賦值運算子也不讓用
    atomic<int> atm3(atm1.load())
    

    (8)store()函式:以原子方式寫入內容

    atomic<int> atm;
    atm.store(1);
    

windows臨界區、其他各種mutex互斥量

  • windows臨界區

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    #define WINDOWS
    
    #ifdef WINDOWS
        CRITICAL_SECTION winsec;//windows中的臨界區,類似于mutex
    #endif
    
    mutex g_mutex;
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息時間在50-1000毫秒之間
            //存放一個物品(這里存放的資料代表物品)
    #ifdef WINDOWS
            EnterCriticalSection(&winsec);//進入臨界區,相當于mutex.lock()
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            LeaveCriticalSection(&winsec);//退出臨界區,相當于mutex.unlock()
    #else
            g_mutex.lock();
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_mutex.unlock();
    #endif // WINDOWS
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//輪詢
    #ifdef WINDOWS
            EnterCriticalSection(&winsec);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            LeaveCriticalSection(&winsec);
    #else
            g_mutex.lock();
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            g_mutex.unlock();
    #endif // WINDOWS
    
        }
    }
    
    int main()
    {
    #ifdef WINDOWS
        InitializeCriticalSection(&winsec);//使用臨界區之前初始化
    #endif // WINDOWS
    
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • 多次進入臨界區試驗

    (1)在同一個執行緒中,windows中相同臨界區變數代表的臨界區的進入(EnterCriticalSection)可以被多次呼叫,但是EnterCriticalSection的呼叫次數需要和LeaveCriticalSection的呼叫次數要相等(否則,該執行緒始終都在臨界區中,該執行緒一直執行,另一個執行緒阻塞)

    (2)c++11中mutex不允許lock多次

  • 自動析構技術

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    #define WINDOWS
    
    //用于自動釋放windows下的臨界區,防止忘記LeaveCriticalSection
    //類似與std::lock_guard
    //RAII類(Resource Acquisition initialization):資源獲取即初始化
    class cWinLock {
    public:
        cWinLock(CRITICAL_SECTION* ptr) :ptr(ptr) {
            EnterCriticalSection(ptr);
        }
        ~cWinLock() {
            LeaveCriticalSection(ptr);
        }
    private:
        CRITICAL_SECTION* ptr;
    };
    
    #ifdef WINDOWS
        CRITICAL_SECTION winsec;//windows中的臨界區,類似于mutex
    #endif
    
    mutex g_mutex;
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息時間在50-1000毫秒之間
            //存放一個物品(這里存放的資料代表物品)
    #ifdef WINDOWS
            cWinLock wLock(&winsec);
            cWinLock wLock_(&winsec);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
    #else
            lock_guard<mutex> lock(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
    #endif // WINDOWS
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//輪詢
    #ifdef WINDOWS
            cWinLock wLock(&winsec);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
    #else
            lock_guard<mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
    #endif // WINDOWS
    
        }
    }
    
    int main()
    {
    #ifdef WINDOWS
        InitializeCriticalSection(&winsec);//使用臨界區之前初始化
    #endif // WINDOWS
    
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • recursive_mutex遞回的獨占互斥量

    std::mutex:獨占互斥量,在本執行緒lock后,本執行緒無法繼續lock【除非unlock后】,其它執行緒也無法lock

    std::recursive_mutex:遞回的獨占互斥量,允許同一個執行緒中同一個互斥量多次被lock【第一次lock后沒有unlock】,效率上比mutex低

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    recursive_mutex g_mutex;
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息時間在50-1000毫秒之間
            //存放一個物品(這里存放的資料代表物品)
            lock_guard<recursive_mutex> lock(g_mutex);
            lock_guard<recursive_mutex> lock_(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//輪詢
            lock_guard<recursive_mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
    
  • 帶超時的互斥量std::timed_mutex和std::recursive_timed_mutex

    1. std::timed_mutex:帶超時功能的獨占互斥量

      (1)try_lock_for():引數是一段時間,等待一段時間,如果等待超時或者lock成功,繼續流程

      #include <iostream>
      #include<thread>
      #include<mutex>
      #include<Windows.h>
      using namespace std;
      
      timed_mutex g_mutex;//帶超時功能的獨占互斥量
      int g_buf[100];//緩沖區,最多存放一百個數
      int g_count = 0;
      
      //第一個執行緒:生產者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一個1……20之間的亂數
              //存放一個物品(這里存放的資料代表物品)
              if (g_mutex.try_lock_for(10ms)) {//等待100毫秒嘗試lock
                  //在100毫秒內lock成功
                  g_buf[g_count] = r;
                  ++g_count;
                  cout << "放入物品:" << r << endl;
                  g_mutex.unlock();
              }
              else {
                  //沒有在100毫秒內lock成功
                  cout << "Producer lock失敗" << endl;
                  this_thread::sleep_for(chrono::microseconds(100));
              }
          }
      }
      
      //第二個執行緒:消費者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));//輪詢
              lock_guard<timed_mutex> lock(g_mutex);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
          }
      }
      
      int main()
      {
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
      

    在這里插入圖片描述

    (2)try_lock_until:引數是一個未來時間點,在這個未來時間沒到的時間內,不管lock是否成功,流程繼續

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    timed_mutex g_mutex;//帶超時功能的獨占互斥量
    int g_buf[100];//緩沖區,最多存放一百個數
    int g_count = 0;
    
    //第一個執行緒:生產者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一個1……20之間的亂數
            //存放一個物品(這里存放的資料代表物品)
            if (g_mutex.try_lock_until(chrono::steady_clock::now() + 100ms)) {//當前時間加上100ms
                //在以當前時刻算起的100ms內,lock成功
                g_buf[g_count] = r;
                ++g_count;
                cout << "放入物品:" << r << endl;
                g_mutex.unlock();
            }
            else {
                cout << "Producer lock失敗" << endl;
                this_thread::sleep_for(chrono::microseconds(100));
            }
        }
    }
    
    //第二個執行緒:消費者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//輪詢
            lock_guard<timed_mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
    
    1. std::recursive_timed:帶超時功能的遞回獨占互斥量

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/195889.html

標籤:C++

上一篇:Python爬蟲入門教程 100-100 我用了800天,才成為爬蟲領域的一個新人

下一篇:懂得都懂。不懂說了也沒用。程式員小笑話

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more