Python 判斷檢查路徑是否存在 exists

本篇介紹 Python 中檢查判斷路徑是否存在 os.path.exists 的用法與範例,在檔案處理中要建立檔案前通常都會判斷檢查路徑是否存在,或者建立資料夾時也是一樣會先判斷檢查路徑是否存在,以上兩個都是很常使用到的功能,趕緊來學習吧!
以下範例是在 Python 3 環境下測試過。

在 Python 中要判斷檔案是否或資料夾是否存在可用 os.path.exists()
exists 如果路徑 path 存在會回傳 True;如果路徑 path 不存在會回傳 False。
使用 os.path.exists 時,需先 import os

Python os.path.exists() 判斷資料夾是否存在

以下範例為 Python 使用 os.path.exists() 來判斷 ~/Desktop 資料夾是否存在,

1
2
3
4
5
6
7
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os

# 判斷資料夾是否存在
dirExist = os.path.exists('~/Desktop')
print(dirExist)

Python os.path.exists() 判斷檔案是否存在

以下範例為 Python 使用 os.path.exists() 來判斷 ~/Download/wget-1.21.tar.gz 檔案是否存在,

1
2
3
4
5
6
7
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os

# 判斷檔案是否存在
fileExist = os.path.exists('~/Download/wget-1.21.tar.gz')
print(fileExist)

建立新檔案/新資料夾前的 os.path.exists() 判斷使用情境

有一種情境會很常使用到 os.path.exists(),那就是要建立新檔案前去檢查判斷有沒有該檔案存在,或者建立新資料夾前去檢查判斷有沒有該資料夾存在。
值得注意一點的是 os.path.exists() 回傳 True 代表該路徑存在,如果單純要判斷該路徑是否為一個檔案應該用 os.path.isfile,單純判斷該路徑是否為一個資料夾應該用 os.path.isdir

以下就來看看 os.path.exists() 的範例,程式碼如下,
如果目錄下有個 pictures 的檔案,則 os.path.exists() 會回傳 True,
如果目錄下有個 pictures 的資料夾,則 os.path.exists() 也會回傳 True,
範例裡是回傳 False 才建立 pictures 資料夾,

python-os-path-exists.py
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os

path = 'pictures'
print(os.path.exists(path))

if not os.path.exists(path):
print('mkdir ' + path)
os.mkdir(path)

輸出結果如下:

1
2
False
mkdir pictures

參考
Python 如何檢查檔案或目錄是否已經存在? - G. T. Wang
https://blog.gtwang.org/programming/python-howto-check-whether-file-folder-exists/
Python Check If File or Directory Exists
https://www.guru99.com/python-check-if-file-exists.html
Python 檢查檔案目錄是否存在
https://www.opencli.com/linux/python-check-file-directory-exists
python如何使用 os.path.exists()–Learning from stackoverflow_Python_Paul_C_V的专栏-CSDN博客
https://blog.csdn.net/Paul_C_V/article/details/45226855
os.path.exists — Common pathname manipulations — Python 3 documentation
https://docs.python.org/3/library/os.path.html#os.path.exists

其它相關文章推薦
Python 判斷檢查檔案是否存在 os.path.isfile
Python 判斷資料夾是否存在 os.path.isdir
Python 取得檔案大小 getsize
Python 取出檔案名稱 basename
Python 取出目錄的路徑 dirname
Python 字串分割 split
Python 連接字串 join
Python 去除空白與去除特殊字元 strip

Python 判斷資料夾是否存在 os.path.isdir

本篇介紹 Python 中檢查判斷資料夾是否存在 os.path.isdir 的用法與範例,在檔案處理中要建立資料夾時會先判斷檢查資料夾是否存在,是個很常使用到的功能,趕緊來學習吧!
以下範例是在 Python 3 環境下測試過。

使用範例

在 Python 中要判斷路徑是否為資料夾可用 os.path.isdir()
isdir 會判斷傳入的路徑是否為一個存在的資料夾,是的話回傳 True,反之回傳 False,
使用 os.path.isdir 時,需先 import os

假設目錄下有 dir1 資料夾與 file.txt 檔案,

1
2
3
$ ls
dir1
file.txt

程式碼如下:

python-os-path-isdir.py
1
2
3
4
5
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os

print(os.path.isdir('dir1'))

使用 isdir 判斷有此資料夾會回傳True

1
True

另外 os.path.exists 也可用,範例如下:

1
print(os.path.exists('dir1'))

使用 exists 判斷有此目錄會回傳 True,反之回傳 False

1
True

但如果用os.path.exists('file.txt')判斷 file.txt 是否存在則會回傳 true,原因是 os.path.exists 適合用來判斷是不是檔案或目錄。

1
True

結論

如果要判斷是否為資料夾且不是檔案的話,請用 isdir,不使用 exists。

參考
os.path — Common pathname manipulations — Python 3.8.2 documentation
https://docs.python.org/3/library/os.path.html#os.path.isdir

其它相關文章推薦
Python 判斷檢查檔案是否存在 os.path.isfile
Python 判斷檢查路徑是否存在 exists
Python 取得檔案大小 getsize
Python 取出檔案名稱 basename
Python 取出目錄的路徑 dirname
Python 字串分割 split
Python 連接字串 join
Python 去除空白與去除特殊字元 strip

Python 判斷檢查檔案是否存在 os.path.isfile

本篇介紹 Python 中檢查判斷路徑是否為檔案 os.path.isfile 的用法與範例,在檔案處理中要建立檔案前通常都會判斷檢查檔案是否存在,是個很常使用到的功能,趕緊來學習吧!
以下範例是在 Python 3 環境下測試過。

使用範例

在 Python 中要判斷是否為檔案可用 os.path.isfile()
isfile 會判斷傳入的路徑是否為一個存在的正規檔案,是的話回傳 True,反之回傳 False,
使用 os.path.isfile 時,需先 import os

假設目錄下檔案有1個 aaa.txt 檔案與1個 ddd 資料夾,

1
2
3
$ ls
ddd
aaa.txt

範例如下:

python-os-path-isfile.py
1
2
3
4
5
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os

print(os.path.isfile('aaa.txt'))

使用 isfile 判斷有此檔案會回傳True,結果如下:

1
True

另外 os.path.exists 也可用,範例如下:

1
print(os.path.exists('aaa.txt'))

使用 exists 判斷有此檔案會回傳 True,反之回傳 False

1
True

但如果用os.path.exists('ddd')判斷 ddd 是否存在則會回傳 true,原因是 os.path.exists 適合用來判斷是不是檔案或目錄。

1
True

結論

在 Python 中如果要判斷是否為檔案且不是目錄的話,請用 isfile,不使用 exists。

參考
os.path — Common pathname manipulations — Python 3.8.2 documentation
https://docs.python.org/3/library/os.path.html#os.path.isfile
Python判断文件是否存在的三种方法 - j_hao104 - 博客园
https://www.cnblogs.com/jhao/p/7243043.html
Python 如何檢查檔案或目錄是否已經存在? - G. T. Wang
https://blog.gtwang.org/programming/python-howto-check-whether-file-folder-exists/
Python | os.path.isfile() method - GeeksforGeeks
https://www.geeksforgeeks.org/python-os-path-isfile-method/

其它相關文章推薦
Python 判斷資料夾是否存在 os.path.isdir
Python 判斷檢查路徑是否存在 exists
Python 取得檔案大小 getsize
Python 取出檔案名稱 basename
Python 取出目錄的路徑 dirname
Python 字串分割 split
Python 連接字串 join
Python 去除空白與去除特殊字元 strip

std::condition_variable 用法與範例

本篇介紹 C++ 的 std::condition_variable 用法,使用 std::condition_variable 的 wait 會把目前的執行緒 thread 停下來並且等候事件通知,而在另外一個執行緒裡我們可以使用 std::condition_variable 的 notify_one 或 notify_all 去發送通知那些正在等待的事件,這在多執行绪程式裡經常使用到,以下將開始介紹 std::condition_variable 的用法,並展示一些範例,建議閱讀以下文章前需先對建立 std::thread 多執行緒std::mutex 鎖有一定程度的熟悉。

需要引入的標頭檔<condition_variable>

以下為 condition_variable 常用的成員函式與說明,
wait:阻塞當前執行緒直到條件變量被喚醒
notify_one:通知一個正在等待的執行緒
notify_all:通知所有正在等待的執行緒

使用 std::condition_variable 的 wait 必須要搭配 std::unique_lock<std::mutex> 一起使用。

範例1. 用 notify_one 通知一個正在 wait 的執行緒

下面的例子是先開一個新的執行緒 worker_thread 然後使用 std::condition_variable 的 wait 事件的通知,
此時 worker_thread 會阻塞(block)直到事件通知才會被喚醒,
之後 main 主程式延遲個 5 ms 在使用 std::condition_variable 的 notify_one 發送,
之後 worker_thread 收到 來自主執行緒的事件通知就離開 wait 繼續往下 cout 完就結束該執行緒,

這裡主程式的延遲 5ms 是避免一開始執行緒還沒建立好來不及 wait 等待通知,主程式就先發送 notify_one 事件通知了,

std-condition_variable.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// g++ std-condition_variable.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;

void worker_thread()
{
std::unique_lock<std::mutex> lock(m);
std::cout << "worker_thread() wait\n";
cond_var.wait(lock);

// after the wait, we own the lock.
std::cout << "worker_thread() is processing data\n";
}

int main()
{
std::thread worker(worker_thread);

std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::cout << "main() notify_one\n";
cond_var.notify_one();

worker.join();
std::cout << "main() end\n";
}

輸出:

1
2
3
4
worker_thread() wait
main() notify_one
worker_thread() is processing data
main() end

本範例是學習了用 notify_one 通知單一個等待的執行緒,
下個範例要介紹的是 notify_all 用來通知所有正在等待的執行緒,

範例2. 用 notify_all 通知全部多個 wait 等待的執行緒

以下範例主要目的是建立5個執行緒並等待通知,
之後主程式執行go函式裡的cond_var.notify_all()去通知所有正在等待的執行緒,也就是剛剛建立的5個執行緒,
這5個執行緒分別收到通知後從wait函式離開,之後檢查ready變數為true就離開迴圈,
接著印出thread id然後結束該執行緒。

std-condition_variable2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// g++ std-condition_variable2.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;
bool ready = false;

void print_id(int id) {
std::unique_lock<std::mutex> lock(m);
while (!ready) {
cond_var.wait(lock);
}
std::cout << "thread " << id << '\n';
}

void go() {
std::unique_lock<std::mutex> lock(m);
ready = true;
cond_var.notify_all();
}

int main()
{
std::thread threads[5];
// spawn 5 threads:
for (int i=0; i<5; ++i)
threads[i] = std::thread(print_id,i);

std::cout << "5 threads ready to race...\n";
go();

for (auto& th : threads)
th.join();

return 0;
}

輸出如下,可以看見這5個執行緒不按順序地收到通知並且各別印出thread id,

1
2
3
4
5
6
5 threads ready to race...
thread 4
thread 1
thread 2
thread 3
thread 0

這個範例多使用了一個額外的 ready 變數來輔助判斷,也間接介紹了cond_var.wait的另一種用法,
使用一個 while 迴圈來不斷的檢查 ready 變數,條件不成立的話就cond_var.wait繼續等待,
等到下次cond_var.wait被喚醒又會再度檢查這個 ready 值,一直迴圈檢查下去,
這技巧在某些情形下可以避免假喚醒這個問題,
簡單說就是「cond_var.wait被喚醒後還要多判斷一個 bool 變數,一定要條件成立才會結束等待,否則繼續等待」。

而這邊的 while 寫法

1
2
3
while (!ready) {
cond_var.wait(lock);
}

可以簡化寫成下面這個樣子,也就是 wait 的另一種用法,
多帶一個謂詞在第二個參數,關於這個寫法不熟悉可以看看這篇,

1
cond_var.wait(lock, []{return ready;});

因為 wait 內部的實作方法如下,等價於上面這種寫法,

1
2
3
4
5
6
template<typename _Predicate>
void wait(unique_lock<mutex>& __lock, _Predicate __p)
{
while (!__p())
wait(__lock);
}

範例3. wait 等待通知且有條件式地結束等待

上個範例簡單介紹了cond_var.wait帶入第二個參數的用法了,所以本範例來實際演練這個用法,

worker_thread裡的cond_var.wait第一參數傳入一個 unique_lock 鎖,
第二個參數傳入一個可(被)呼叫的物件,來判斷是否要停止等待;這個可(被)呼叫的物件的需要回傳一個 bool 變數,
如果回傳 true 的話,condition_variable 就會停止等待、繼續往下執行,
如果回傳 false 的話,則會重新開始等待下一個通知。
因此等價於 while (!pred()) { wait(lock); }

這邊要注意 main 裡是有一個 lock_guard 與 unique_lock,worker_thread 裡有一個 unique_lock。

std-condition_variable3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// g++ std-condition_variable3.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lock(m);
std::cout << "worker_thread() wait\n";
cond_var.wait(lock, []{return ready;});

// after the wait, we own the lock.
std::cout << "worker_thread() is processing data\n";
data += " after processing";

// Send data back to main()
processed = true;
std::cout << "worker_thread() signals data processing completed\n";

// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lock.unlock();
cond_var.notify_one();
}

int main()
{
std::thread worker(worker_thread);

data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lock(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cond_var.notify_one();

// wait for the worker
{
std::unique_lock<std::mutex> lock(m);
cond_var.wait(lock, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';

worker.join();
}

程式輸出結果如下:

1
2
3
4
5
main() signals data ready for processing
worker_thread() wait
worker_thread() is processing data
worker_thread() signals data processing completed
Back in main(), data = Example data after processing

範例4. 典型的生產者與消費者的範例

在設計模式(design pattern)中,這是一個典型的生產者與消費者(producer-consumer)的例子,
範例裡有一位生產者每1秒生產了1個東西放到 condvarQueue 裡,
這個 condvarQueue 會在去通知消費者,消費者收到通知後從 queue 裡拿出這個東西來作事情。

std-condition_variable4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// g++ std-condition_variable4.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <queue>
#include <chrono>
#include <mutex>
#include <condition_variable>

class condvarQueue
{
std::queue<int> produced_nums;
std::mutex m;
std::condition_variable cond_var;
bool done = false;
bool notified = false;
public:
void push(int i)
{
std::unique_lock<std::mutex> lock(m);
produced_nums.push(i);
notified = true;
cond_var.notify_one();
}

template<typename Consumer>
void consume(Consumer consumer)
{
std::unique_lock<std::mutex> lock(m);
while (!done) {
while (!notified) { // loop to avoid spurious wakeups
cond_var.wait(lock);
}
while (!produced_nums.empty()) {
consumer(produced_nums.front());
produced_nums.pop();
}
notified = false;
}
}

void close()
{
{
std::lock_guard<std::mutex> lock(m);
done = true;
notified = true;
}
cond_var.notify_one();
}
};

int main()
{
condvarQueue queue;

std::thread producer([&]() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "producing " << i << '\n';
queue.push(i);
}
queue.close();
});

std::thread consumer([&]() {
queue.consume([](int input){
std::cout << "consuming " << input << '\n';
});
});

producer.join();
consumer.join();
}

程式輸出結果如下:

1
2
3
4
5
6
7
8
9
10
producing 0
consuming 0
producing 1
consuming 1
producing 2
consuming 2
producing 3
consuming 3
producing 4
consuming 4

使用上的小細節

看了很多範例,通知端執行緒 notify_one 通知前到底要不要加鎖?
如果要加鎖要加 unique_lock 還 lock_guard 呢?

我的經驗是
如果不需要修改共享變數,則 notify_one/notify_all 通知前不用加鎖,
示意如下:

1
2
3
4
Thread A                Thread B
unique_lock lock(m)
cond.wait()
cond.notify_one()

如果有需要修改共享變數,則 notify_one/notify_all 通知前要加鎖,
加鎖的範圍不用包含到 cond.notify_one/cond.notify_all,
注意這邊的鎖是要保護共享資料,而不是 cond.notify_one/cond.notify_all,
示意如下:

1
2
3
4
5
6
7
8
Thread A                Thread B
unique_lock lock(m)
cond.wait(lock, []{return ready;})
{
lock_guard lock(m)
ready = true
}
cond.notify_one()

這兩者有效能上的差異,這部分我們以後有機會來細說談談。

重點歸納

簡單歸納一下幾個重點,
等待的執行緒應有下列幾個步驟:

  1. 獲得 std::unique_lock 鎖,並用該鎖來保護共享變數。
  2. 下面三步驟或使用 predicate 的 wait 多載版本,
    2-1. 檢查有沒有滿足結束等待的條件,以預防資料早已經被更新與被通知了。
    2-2. 執行 wait 等待,wait 操作會自動地釋放該 mutex 並且暫停該執行緒。
    2-3. 當 condition variable 通知時,該執行緒被喚醒,且該mutex自動地被重新獲得,該執行緒應該檢查一些條件決定要不要繼續等待。

通知的執行緒應有下列幾個步驟:

  1. 獲取一個 std::mutex (通常透過std::lock_guard來取得)。
  2. 在上鎖的範圍內完成變數的修改。
  3. 執行 std::condition_variable 的notify_one/notify_all (不需被該鎖包覆)。

參考
[1] std::condition_variable - cppreference.com
https://en.cppreference.com/w/cpp/thread/condition_variable
[2] condition_variable - C++ Reference - cplusplus.com
http://www.cplusplus.com/reference/condition_variable/condition_variable/
[3] 邁向王者的旅途: [C++] Use std::condition_variable for Parallellism
https://shininglionking.blogspot.com/2018/08/c-use-stdconditionvariable-for.html
[4] C++11 Thread 的 condition variable – Heresy’s Space
https://kheresy.wordpress.com/2014/01/09/c11-condition-variable/
[5] C++/STL/ConditionVariable - 維基教科書,自由的教學讀本
https://zh.wikibooks.org/zh-tw/C%2B%2B/STL/ConditionVariable
[6] Is this use of condition variable safe (taken from cppreference.com)
https://stackoverflow.com/questions/61104388/is-this-use-of-condition-variable-safe-taken-from-cppreference-com

謂詞函數predicate相關文章
[1] 謂詞函數_百度百科
https://baike.baidu.com/item/%E8%B0%93%E8%AF%8D%E5%87%BD%E6%95%B0/7501851
謂詞函數是一個判斷式,一個返回bool值的函數或者仿函數。
[2] C++ 具名要求: 謂詞 (Predicate) - cppreference.com
https://zh.cppreference.com/w/cpp/named_req/Predicate
[3]函數對象 - 維基百科,自由的百科全書
https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%AF%B9%E8%B1%A1
[4] C++ 標準程式庫的函式物件 | Microsoft Docs
https://docs.microsoft.com/zh-tw/cpp/standard-library/function-objects-in-the-stl?view=vs-2019
這裡翻譯為述詞Predicate,是個傳回布林值的函式物件。

pthread 相關文章
[1] pthread_cond_wait 为什么需要传递mutex 参数? - 知乎
https://www.zhihu.com/question/24116967
其中以黃兢成網友回覆的最正確與明瞭
[2] c++ - Calling pthread_cond_signal without locking mutex - Stack Overflow
https://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex
討論pthread_cond_signal是否要加鎖,但最佳解答似乎不是很正確
[3] pthread_cond_wait.c source code [glibc/nptl/pthread_cond_wait.c] - Woboq Code Browser
https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html
在 trace pthread_cond_wait 後發現在內部確實會釋放該 mutex 鎖 __pthread_mutex_unlock_usercnt,並在喚醒時獲得該 mutex 鎖 __pthread_mutex_cond_lock

其它相關文章推薦
C/C++ 新手入門教學懶人包
std::condition_variable 怎麼實作的?
std::thread 用法與範例
std::mutex 用法與範例
std::vector 用法與範例
std::deque 用法與範例

C++ std::queue 用法與範例

本篇介紹 C++ 的 std::queue 用法,C++ std::queue 教學如下:

std::queue 是具有 FIFO 特性的容器配接器, 應用在有先進先出的情形。
queue 是一層容器的包裝, 背後是用 deque 實現的, 並且只提供特定的函數接口。

以下內容將分為這幾部份,

  • queue 常用功能
  • C++ queue 範例
  • queue 的優點與缺點

c++ 要使用 queue 容器的話,需要引入的標頭檔<queue>

queue 常用功能

以下為 std::queue 內常用的成員函式
push:把值加到尾巴
pop:移除頭的值
back:回傳尾巴的值
front:回傳頭的值
size:回傳目前長度
empty:回傳是否為空

C++ queue 範例

以下為 c++ queue 的各種操作用法,把元素加進 queue 的尾部使用 push()
把元素從 queue 頭部取出用 pop(),注意取出會將該元素從 queue 移除,
取得 queue 的最尾巴的元素使用 back()
取得 queue 的最頭部的元素使用 front(),注意取得並不會將該元素從 queue 移除,
取得 queue 目前裡面有幾個元素使用 size()

std-queue.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// g++ std-queue.cpp -o a.out -std=c++11
#include <iostream>
#include <queue>

using namespace std;

int main() {
queue<int> q;
q.push(1); // [1]
q.push(2); // [1, 2]
q.push(3); // [1, 2, 3]

cout << q.front() << endl; // 1
cout << q.back() << endl; // 3
cout << q.size() << endl; // 3

int a = q.front(); // copy
int &b = q.front(); // reference

cout << q.front() << " " << &q.front() << endl; // 印記體位置
cout << a << " " << &a << endl;
cout << b << " " << &b << endl; // 與 q.front() 記憶體位置相同

// 印出 queue 內所有內容
int size = q.size();
for (int i = 0; i < size; i++) {
cout << q.front() << " ";
q.pop();
}
cout << "\n";

// 印出 queue 內所有內容
/*while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << "\n";*/

return 0;
}

輸出內容如下:

1
2
3
4
5
6
7
1
3
3
1 0xb77c70
1 0x7ffe63ead460
1 0xb77c70
1 2 3

queue 的優點與缺點

queue 的優點

  • 快速的把頭的值拿掉

queue 的缺點

  • 只能操作頭跟尾, 不能取得中間的值(根據FIFO特性)

參考
std::queue - cppreference.com
https://en.cppreference.com/w/cpp/container/queue
queue - C++ Reference
http://www.cplusplus.com/reference/queue/queue/
Queue in Standard Template Library (STL) - GeeksforGeeks
https://www.geeksforgeeks.org/queue-cpp-stl/

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::deque 用法與範例
std::vector 用法與範例
std::map 用法與範例
std::unordered_map 用法與範例
std::thread 用法與範例
std::mutex 用法與範例
std::condition_variable 用法與範例
std::sort 用法與範例
std::find 用法與範例
C++ 計算程式執行時間

C++ std::vector 用法與範例

本篇 ShengYu 介紹 C++ 的 std::vector 用法,C++ vector 是一個可以改變陣列大小的序列容器。C++ vector 是陣列的升級版,主要因為 vector 能高效地對記憶體進行管理以及動態增長。vector 其實就是將陣列和方法封裝形成的一個類別。

vector 底層實現是一個連續記憶體空間,當容量不夠的時候就會重新申請空間,並把原本資料複製或搬移到新的空間。

vector 的容器大小可以動態增長,但是並不意味著每一次插入操作都進行 reallocate。記憶體的分配與釋放耗費的資源是比較大的,因此應該減少它的次數。這也就意味著容器的容量(capacity)與容器目前容納的大小(size)是不等的,前者應大於後者。

vector 分配新的空間時,容量(capacity)可能為原有容量的 2 倍或者原有容量的 1.5 倍,各個編譯器可能不同,稍後會介紹。

以下 C++ vector 內容將分為這幾部份,

  • vector 常用功能
  • vector 初始化
  • 存取 vector 元素的用法
  • 在 vector 容器尾巴新增元素的用法
  • 在 vector 容器尾巴移除元素的用法
  • vector for 迴圈遍歷
  • vector 實際範例
  • vector 使用 [] operator 與 at() 的差異
  • vector size() 與 capacity() 的差異
  • vector reserve() 預先配置容器大小的用法
  • vector shrink_to_fit() 收縮的用法
  • vector resize() 的用法
  • 兩個 vector 串連
  • vector 的優點與缺點
  • vector 使用小技巧

C++ 要使用 vector 容器的話,需要引入的標頭檔<vector>

vector 常用功能

以下為 C++ std::vector 內常用的成員函式,
push_back:把元素加到尾巴,必要時會進行記憶體配置
pop_back:移除尾巴的元素
insert:插入元素
erase:移除某個位置元素, 也可以移除某一段範圍的元素
clear:清空容器裡所有元素
size:回傳目前長度
empty:回傳是否為空
[i]:隨機存取索引值為i的元素,跟陣列一樣索引值從 0 開始
at(i):隨機存取索引值為i的元素,跟上面 operator[] 差異是 at(i) 會作邊界檢查,存取越界會拋出一個例外
reserve():預先配置大小

vector 初始化

這邊介紹 C++ 幾種 vector 初始化,
這樣是宣告一個 int 整數類型的 vector,裡面沒有任何元素(空),size 為 0 表示 vector 容器中沒有任何元素,capacity 也是 0,

1
2
3
4
5
6
7
#include <vector>
using namespace std;

int main() {
vector<int> v;
return 0;
}

先宣告一個空的 vector,再透過 push_back 將資料一直推進去,

1
2
3
4
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

你也可以寫成一行,但這語法需要編譯器 C++11 支援,

1
vector<int> v = {1, 2, 3};

或者是這樣寫也可以,

1
vector<int> v({1, 2, 3});

假如要從另外一個 vector 容器複製資料過來當作初始值的話可以這樣寫,

1
2
vector<int> v1 = {1, 2, 3};
vector<int> v2 = v1;

或者這樣,

1
2
vector<int> v1 = {1, 2, 3};
vector<int> v2(v1);

也可以從傳統陣列裡複製過來當作初始值,

1
2
int n[3] = {1, 2, 3};
vector<int> v(n, n+3);

不想複製來源 vector 全部的資料,想要指定複製 vector 的範圍的話也可以,例如我要複製 v1 vector 的第三個元素到倒數第二個元素,

1
2
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2(v1.begin()+2, v1.end()-1); // {3, 4}

如果是指定複製傳統陣列的範圍的話,可以這樣寫,

1
2
int n[5] = {1, 2, 3, 4, 5};
vector<int> v(n+2, n+4); // {3, 4}

存取 vector 元素的用法

vector 用 [] 來隨機存取元素,第一個元素為 v[0],索引值是 0,第二個元素為 v[1],索引值是 1,依此類推,[] 不只可以讀取元素也可以用來修改元素,例如 v[0] = 4 像下面範例這樣寫,

1
2
3
4
5
vector<int> v = {1, 2, 3};
cout << v[0] << "\n"; // 1
cout << v[1] << "\n"; // 2
v[0] = 4;
cout << v[0] << "\n"; // 4

在 vector 容器尾巴新增元素的用法

在前面已經有稍微透漏了怎麼新增 vector 元素的方法了,沒錯就是用 push_back() 這個方法,它會把元素加在 vector 容器的尾巴,
先宣告一個空的 vector,再透過 push_back 將資料一直推進去,

1
2
3
4
vector<int> v = {1, 2, 3};
v.push_back(4); // {1, 2, 3, 4}
v.push_back(5); // {1, 2, 3, 4, 5}
v.push_back(6); // {1, 2, 3, 4, 5, 6}

在 vector 容器尾巴移除元素的用法

移除 vector 容器尾巴的元素用 pop_back(),一次只能從尾端移除一個元素,不能指定移除的數量,

1
2
3
vector<int> v = {1, 2, 3};
v.pop_back(); // {1, 2}
v.pop_back(); // {1}

vector for 迴圈遍歷

以下介紹 vector 的 for 的三種遍歷寫法,第一種是一般很常見的寫法,

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

using namespace std;

int main() {
vector<int> vec({1, 2, 3});
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << " ";
}
cout << "\n";
return 0;
}

第二種是使用 iterator 迭代器來印出 vector 內所有內容,其中 vector<int>::iterator it 可以簡化寫成 auto it = vec.begin() 這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>

using namespace std;

int main() {
vector<int> vec({1, 2, 3});
// for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
// or
for (auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
cout << "\n";
return 0;
}

第三種是個很方便的寫法,c++11 才有支援,適合追求快速(懶惰)的人,相較於第一種的優點是不用多寫陣列索引去存取,直接就當變數使用,

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

using namespace std;

int main() {
vector<int> vec({1, 2, 3});
for (auto &v : vec) {
cout << v << " ";
}
cout << "\n";
return 0;
}

vector 實際範例

實際的範例寫起來像這樣,

std-vector.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// g++ std-vector.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>

using namespace std;

int main() {
vector<int> vec; // 宣告一個放 int 的 vector

vec.push_back(1); // {1}
vec.push_back(2); // {1, 2}
vec.push_back(3); // {1, 2, 3}
vec.push_back(4); // {1, 2, 3, 4}
vec.push_back(5); // {1, 2, 3, 4, 5}

vec.pop_back(); // {1, 2, 3, 4} 移除尾巴的值
vec.pop_back(); // {1, 2, 3}

cout << "size: " << int(vec.size()) << endl; // 印出大小

// 印出 vector 內所有內容
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << " ";
}
cout << "\n";

// 用 iterator 來印出 vector 內所有內容
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
cout << "\n";

vec[0] = 99; // {99, 2, 3} 改變裡面的值

vector<int>::iterator it = vec.begin();
vec.insert(it+2, 6); // {99, 2, 6, 3}
vec.erase(it+2); // {99, 2, 3}

// 快速(懶人)寫法, c++11 才支援
for (auto &v : vec) {
cout << v << " ";
}
cout << "\n";

return 0;
}

輸出內容如下:

1
2
3
4
size: 3
1 2 3
1 2 3
99 2 3

vector 使用 [] operator 與 at() 的差異

C++ std::vector 提供了 [] operator 的方式讓我們在取得元素時就像 C-style 陣列那樣使用,另外 std::vector 還提供了 at() 這個方法也是可以取得元素,那這兩種方式到底有什麼差別?

[] operator 在回傳元素時是不會作任何的邊界檢查,而在 at() 取得元素時會作邊界的處理,如果你存取越界時 std::vector 會拋出一個 out_of_range 例外,例外處理可以參考我的另一篇文章, 所以 at() 提供了較為安全的存取方式。

[] operator 隨機存取與 at() 各有好壞,使用上時挑選符合需求的方式。

vector size() 與 capacity() 的差異

vector 使用 size() 是取得目前 vector 裡的元素個數,vector 使用 capacity() 是取得目前 vector 裡的預先配置的空間大小,當容量(capacity)空間不夠使用時 vector 就會重新申請空間,容量(capacity)會增加為原來的 2 倍或 1.5 倍,例如:1、2、4、8、16、32 增長下去,各個編譯器可能不同,來看看下面範例,

1
2
3
4
5
6
7
8
9
10
11
12
vector<int> v;
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";

輸出結果如下,從以下的輸出可以發現在我使用的 clang 編譯器中 capacity 是以 1、2、4、8 兩倍的方式增長下去,

1
2
3
4
5
6
size=0, capacity=0
size=1, capacity=1
size=2, capacity=2
size=3, capacity=4
size=4, capacity=4
size=5, capacity=8

vector reserve() 預先配置容器大小的用法

vector 使用 reserve() 是預留空間的意思,如果我們一開始就知道容器的裡要放置多少個元素的話,可以透過 reserve() 來預先配置容器大小,這樣可以減少一直配置記憶體的機會。

如下例所示,先宣告一個 int 的 vector,假設我想要預先配置好 5 個大小的話可以這樣寫 vector.reserve(5),這樣會預留 5 個元素的空間,使用 capacity() 會得到 5,但裡面還沒有任何元素所以使用 size() 會得到 0,之後用 push_back 將元素推進去,然後我們來觀察看看 size 與 capacity 的變化,

之後將 vector push_back 2 個元素進去,再次使用 capacity() 還是 5,而使用 size() 會得到 2,

1
2
3
4
5
6
7
vector<int> v;
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.reserve(5);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";

輸出如下,

1
2
3
size=0, capacity=0
size=0, capacity=5
size=2, capacity=5

那 vector reserve 預留 5 個元素的空間後,之後使用超過 5 個元素 capacity 會發生什麼變化呢?

1
2
3
4
5
6
7
8
9
10
11
vector<int> v;
v.reserve(5);
v.push_back(1);
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
v.push_back(1);
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";

輸出如下,可以發現當 vector 的元素超過預留的 5 個元素時,會將容量增長為原本 capacity 的兩倍,

1
2
3
size=2, capacity=5
size=5, capacity=5
size=6, capacity=10

在 vector 建構子帶入數量 n 會初始化 n 個元素且預設初始值為 0,所以使用 size() 會回傳 n,跟上述的 reserve() 用途是不一樣的,詳見下列範例,

1
2
3
4
5
6
7
8
9
10
vector<int> v(2);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1); // {0, 0, 1}
v.push_back(2); // {0, 0, 1, 2}
v.push_back(3); // {0, 0, 1, 2, 3}
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << "\n";

輸出如下,一開始在 vector 建構子帶入的數量 2 會初始化 2 個元素,

1
2
3
size=2, capacity=2
size=5, capacity=8
0 0 1 2 3

vector shrink_to_fit() 收縮的用法

呈上述 reserve 例子,這時 vector 再使用 shrink_to_fit 成員函式的話,會釋放(free)那些尚未使用的空間,

1
2
3
4
5
6
7
8
vector<int> v;
v.reserve(5);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.push_back(1);
v.push_back(1);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.shrink_to_fit();
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";

輸出如下,可以發現 vector 使用 shrink_to_fit() 後,容量 capacity 收縮回目前元素的 2 個數大小,

1
2
3
size=0, capacity=5
size=2, capacity=5
size=2, capacity=2

如果 size() 剛好等於 capacity() 的話,那麼使用 shrink_to_fit() 則不會有空間被釋放。

vector resize() 的用法

vector 使用 resize 跟 reserve 不太一樣,resize 變大時會把多的元素補 0,例如:

1
2
3
4
5
6
7
vector<int> v;
v.resize(5);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << "\n";

輸出如下,印出來的元素都是 0,

1
2
size=5, capacity=5
0 0 0 0 0

resize 如果要順便指定元素初始值的話,可以將初始值帶入 resize() 的第二個引數,像這樣寫,

1
2
3
4
5
6
7
vector<int> v;
v.resize(5, 10);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << "\n";

輸出如下,這些新增的元素初始值都設成 10 了,

1
2
size=5, capacity=5
10 10 10 10 10

如果 resize 的大小超過 capacity 容量大小會怎麼樣呢?

1
2
3
4
5
6
7
8
vector<int> v = {1, 2, 3};
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
v.resize(5);
cout << "size=" << v.size() << ", capacity=" << v.capacity() << "\n";
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << "\n";

輸出如下,原本的 1, 2, 3 元素有保留以外,剩下新增的元素補 0,

1
2
3
size=3, capacity=3
size=5, capacity=6
1 2 3 0 0

兩個 vector 串連

這邊介紹 C++ 如何將兩個 vector 串連,最簡單方式就是寫迴圈一個一個複製過去,這邊要介紹更方便的方法,就是用 std::copy,例如:兩個 vector 分別為 src 與 dst,那麼可透過 std::copy 將 src 的內容複製到 dst 後面,大致用法如下,

1
std::copy(src.begin(), src.end(), std::back_inserter(dst));

要使用 std::copy 需要引入的標頭檔 <algorithm>,使用 std::back_inserter 需要引入的標頭檔 <iterator>

以下實際範例示範兩個 vector 串連,將 vec1 的內容複製到 vec2 後面,然後再將 vec2 印出來,

std-vector2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ std-vector2.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

int main() {
vector<int> vec1 = {1, 2, 3};
vector<int> vec2 = {4, 5, 6};

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2));

for (auto &v : vec2) {
cout << v << " ";
}
cout << "\n";
return 0;
}

輸出結果如下,

1
4 5 6 1 2 3

vector 的優點與缺點

vector 的優點

  • 宣告時可以不用確定大小
  • 節省空間
  • 支持隨機訪問[i]

vector 的缺點

  • 進行插入刪除時效率低
  • 只能在末端進行 pop 和 push

vector 使用小技巧

使用 vector 時提前分配足夠的空間以避免不必要的重新分配記憶體和搬移資料

開發者喜歡使用 vector,因為他們只需要往向容器中添加元素,而不用事先操心容器大小的問題。但是由一個容量為 0 的 vector 開始往裡面持續添加元素會花費大量的運行性能。如果你預先就知道 vector 需要保存多少元素,就應該提前為其分配足夠的空間reserve()以提升性能。

以上就是 C++ std::vector 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

參考
std::vector - cppreference.com
https://en.cppreference.com/w/cpp/container/vector
vector - C++ Reference
http://www.cplusplus.com/reference/vector/vector/
Vector in C++ STL - GeeksforGeeks
https://www.geeksforgeeks.org/vector-in-cpp-stl/

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::string 用法與範例
std::queue 用法與範例
std::stack 用法與範例
std::set 用法與範例
std::map 用法與範例

printf 印出 std thread id 的數字或字串

之前介紹過如何取得 C++11 std thread id, 本篇要來介紹 printf 如何印出 std::thread::id,cout 本來就可以印 thread id,例如:cout<<std::this_thread::get_id()<<endl;,但是要用 printf 或 sprintf 就不得不把 thread id 轉成數字了,以下為紀錄我使用過的方式。

如果想看之前介紹的如何取得 C++11 std thread id,請看這篇

範例. 不介意真正的 thread id 數字,只是想標記或區別 thread 執行緒的話

使用 hash 的方式,如下所示:

1
std::hash<std::thread::id>{}(std::this_thread::get_id())

範例. 用 stringstream 來轉成字串

1
2
3
std::stringstream ss;
ss << std::this_thread::get_id();
printf("thread id = %s", ss.str().c_str());

範例. 用 stringstream 來轉成數字

1
2
3
std::stringstream ss;
ss << std::this_thread::get_id();
uint64_t id = std::stoull(ss.str());

參考
c++ - How to get integer thread id in c++11 - Stack Overflow
https://stackoverflow.com/questions/7432100/how-to-get-integer-thread-id-in-c11

相關文章
C/C++ 新手入門教學懶人包
std::thread 用法與範例

如何取得 C++11 thread id

本篇介紹如何取得 C++11 的 std::thread::id,有時候在 C++ 多執行緒的情況下,我們會需要印出 thread id 以方便判斷各自是哪個執行緒,以下範例就是簡單的取得 std::thread::id 的範例。

範例1. 取得 std::thread::id

std-thread-get-thread-id1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// g++ std-thread-get-thread-id1.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <chrono>
#include <assert.h>

using namespace std;

void foo() {
std::thread::id tid = std::this_thread::get_id();
cout << "foo thread id : " << tid << "\n";
}

int main() {
std::thread t1(foo);

std::thread::id tid = t1.get_id();
std::thread::id mainTid = std::this_thread::get_id();

if (t1.joinable())
t1.join();

cout << "t1 tid: " << tid << endl;
cout << "main tid: " << mainTid << endl;

return 0;
}

輸出

1
2
3
foo thread id : 140532863575808
t1 tid: 140532863575808
main tid: 140532880869184

範例2. join 與 detach 的 std::thread::id

std-thread-get-thread-id2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// g++ std-thread-get-thread-id2.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <chrono>
#include <assert.h>

using namespace std;

void foo() {
cout << "foo start\n";
std::thread::id tid = std::this_thread::get_id();
cout << "foo thread id : " << tid << "\n";
cout << "foo end\n";
}

int main() {
std::thread t1(foo);

std::thread::id tid = t1.get_id();

if (t1.joinable())
t1.join();

cout << "Thread from Main : " << tid << endl;

std::thread t2(foo);

t2.detach();

std::thread::id tid2 = t2.get_id();

assert(tid2 == std::thread::id());

cout << "sleep 2s\n";
std::this_thread::sleep_for(std::chrono::seconds(2));

cout << "Thread from Main : " << tid2 << endl;

return 0;
}

輸出

1
2
3
4
5
6
7
8
9
foo start
foo thread id : 140447362262784
foo end
Thread from Main : 140447362262784
sleep 2s
foo start
foo thread id : 140447362262784
foo end
Thread from Main : thread::id of a non-executing thread

參考
How to get a Thread ID ?
https://thispointer.com/c11-how-to-get-a-thread-id/

相關文章
C/C++ 新手入門教學懶人包
std::thread 用法與範例

std::this_thread::sleep_for 用法與範例

本篇介紹 C++ 的 std::this_thread::sleep_for 暫停/停止/延遲當前執行緒的用法教學,std::this_thread::sleep_for 的作用是阻塞當前執行緒的執行,直到給定的這段延遲時間過去,其他執行緒並不受影響依舊執行,在文章後面會提供一些入門常用的 std::this_thread::sleep_for 範例程式碼。

自從 C++11 出來後,真是造就跨平台程式的一大福音呀!在這之前 Windows 要用 Sleep,Unix-like 要用 sleep / usleep,跨平台可能使用 define 區隔,哪個平台要用對應的 api,或使用 boost 提供的,現在只要編譯器支援 C++11,就可以輕鬆的用 C++11 的 sleep_for 來完成這件事,且這個程式要換個平台編譯只需要確定該平台編譯器的版本有支援,就免改程式碼就可以無痛移植了。早期 C++11 剛出來時編譯器都未支援,當時還沒這麽普及,不過現在基本上幾乎都支援了。

需要引入的標頭檔<thread>,另外通常也會順便引入<chrono>,因為很常會使用到 chrono 相關的函式,

範例1. 各種不同時間單位的 sleep 範例

以下範例展示了各種不同時間單位的 sleep 範例,有秒(seconds)、毫秒(milliseconds)、微秒(microseconds)、納秒(nanoseconds)共四種,根據不同種情形使用不同時間單位。

1
2
3
4
std::this_thread::sleep_for(std::chrono::seconds(5)); // sleep 5 秒(seconds)
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // sleep 5 毫秒(milliseconds)
std::this_thread::sleep_for(std::chrono::microseconds(5)); // sleep 5 微秒(microseconds)
std::this_thread::sleep_for(std::chrono::nanoseconds(5)); // sleep 5 納秒(nanoseconds)


範例2. 量測/驗證 sleep 到底 sleep 多久

以下範例使用高精準的 high_resolution_clock 來去量測 sleep 2秒究竟有沒有到2秒,這方法也可以拿去量其他程式所執行的時間。

std-this_thread-sleep_for.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ std-this_thread-sleep_for.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <chrono>

int main() {
auto t1 = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(2));
auto t2 = std::chrono::high_resolution_clock::now();

std::chrono::duration<double, std::milli> elapsed = t2 - t1;
std::cout << "Waited " << elapsed.count() << " ms\n";

return 0;
}

在我的 Ubuntu 上的結果輸出為:

1
Waited 2000.07 ms


參考
[1] std::this_thread::sleep_for - cppreference.com
https://en.cppreference.com/w/cpp/thread/sleep_for
[2] sleep_for - C++ Reference
http://www.cplusplus.com/reference/thread/this_thread/sleep_for/
[3] std::this_thread::sleep_for 与std::this_thread::yield的区别
https://blog.csdn.net/Sandy_WYM_/article/details/83538635


相關文章
C/C++ 新手入門教學懶人包
std::thread 用法與範例
std::mutex 怎麼實作的?
std::condition_variable 用法與範例

C++ std::async 用法與範例

本篇介紹 C++ 的 std::async 非同步函式的用法教學,並提供一些入門常用的範例程式碼。

首先介紹 std::async 的兩種模式,分為 async 與 deferred,白話一點的解釋如下:
std::launch::async:非同步模式,當下就建立執行緒去運算生成資料,當別人來跟我要資料時我可能已經運算完這些資料了(在呼叫 std::async 時建立新執行緒去執行運算)。
std::launch::deferred:拖延/懶惰模式,別人來跟我要資料時,我才去建立執行緒運算生成這些資料(在呼叫 std::async 時還不會建立新執行緒,而是呼叫 std::future 的函式時才開始建立新執行緒去運算執行)。
需要引入的標頭檔<future>

本篇內容將分為:

  • 範例1. async (async policy)
  • 範例2. async (deferred policy)
  • 範例3. async (default policy)
  • async 與 thread 的差異之處

範例1. async (async policy)

以下範例為 async policy 的示範,中譯叫非同步策略,意思是同時進行,main() 主執行緒呼叫 std::async 建構子立即返回後即繼續執行,std::async 會將這個 foo() 丟到另外的執行緒去執行,所以以下的範例輸出將會有兩種情形,一種是主執行緒很快的就印出 --1--,另一種情形是先印出 a3 再印出 --1--

std-async1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ std-async1.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <future>

using namespace std;

void foo(const std::string& str) {
std::cout << str << endl;
}

int main() {
auto a1 = std::async(std::launch::async, foo, "a1");
cout << "--1--\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "--2--\n";
a1.get();
cout << "--3--\n";
return 0;
}

輸出

1
2
3
4
a1--1--

--2--
--3--

or

1
2
3
4
--1--
a1
--2--
--3--


範例2. async (deferred policy)

以下範例是 async 使用 deferred policy 這個策略,什麼是deferred policy 呢?deferred policy 就是拖延/懶惰模式,簡單說就是別人來跟我要資料時,我才去運算生產/運算這些資料,這裡的 wait() 或者 get() 就是別人來跟我要資料的比喻。
當 a2.get() or a2.wait() 被呼叫時,才去同步執行 foo() 印出 “a2”。
future 的結果有三種方式:
get():等待非同步執行結束並回傳結果。
wait():等待非同步執行結束,沒有回傳值。
wait_for():超過等待時間回傳結果。

std-async2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ std-async2.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <future>

using namespace std;

void foo(const std::string& str) {
std::cout << str << endl;
}

int main() {
auto a2 = std::async(std::launch::deferred, foo, "a2");
cout << "--1--\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "--2--\n";
a2.wait(); // 印出 a2
cout << "--3--\n";
return 0;
}

輸出

1
2
3
4
--1--
--2--
a2
--3--

這個功能在實作爬蟲時特別好用,例如:一次同時爬 n 個網站。


範例3. async (default policy)

以下為 default policy 的範例,default policy 就是 async policy 或 deferred policy 兩種其中一種,基本上 default policy 就是前兩種範例的混合體囉!所以在輸出的結果上也會有兩種情形。

std-async3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ std-async3.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <future>

using namespace std;

void foo(const std::string& str) {
std::cout << str << endl;
}

int main() {
auto a3 = std::async(foo, "a3");
cout << "--1--\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "--2--\n";
cout << "--3--\n";
return 0;
}

輸出

1
2
3
4
a3--1--

--2--
--3--

or

1
2
3
4
--1--
a3
--2--
--3--

async 與 thread 的差異之處

要取得運算結果時,使用 thread 要取得運算結果通常是在全域變數宣告一個變數,之後使用 join() 再將得到這個變數值,過程比較繁瑣些,使用 async 的話,可以透過回傳的 future 去取得這個運算結果,非常方便。

接下來下篇會介紹實際上什麼情況會需要用到 async 與應用場合及範例。


參考
[1] std::async - cppreference.com - C++ Reference
https://en.cppreference.com/w/cpp/thread/async
[2] async - C++ Reference
http://www.cplusplus.com/reference/future/async/
[3] C++11 程式的平行化:async 與 future
https://kheresy.wordpress.com/2016/03/14/async-and-future-in-c11/
[4] C++ 使用 Async 非同步函數開發平行化計算程式教學
https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/
[5] C++並發實戰13:std::future、std::async、std::promise、std::packaged_task - wanghualin033的博客 - CSDN博客
https://blog.csdn.net/wanghualin033/article/details/89421221
[6] Futures with std::async
http://jakascorner.com/blog/2016/02/futures.html
[7] std::async 的两个坑 - 知乎
https://zhuanlan.zhihu.com/p/39757902
在 std::future 解構時會 block 直到非同步的執行結束,所以用 lambda expression 時需注意。


相關文章
C/C++ 新手入門教學懶人包
std::thread 用法與範例