Làm quen với Multithreading (P2)
Như trong Phần 1. Các bạn đã được làm quen với các khái niệm khi sử dụng thread như init, join, joinable, detach thread. Phần này xin được tiếp tục giới thiệu về các khái niệm tiếm theo như Thread ID, name space, Concurrent , mutex... để có 1 cái nhìn tổng quan hơn về multithreading trước khi ...
Như trong Phần 1. Các bạn đã được làm quen với các khái niệm khi sử dụng thread như init, join, joinable, detach thread. Phần này xin được tiếp tục giới thiệu về các khái niệm tiếm theo như Thread ID, name space, Concurrent , mutex... để có 1 cái nhìn tổng quan hơn về multithreading trước khi chúng ta đi vào ứng dụng thực tế.
Mọi thread đều có 1 unique identifier. Và trong class thread có 1 public function cho phép get ra giá trị của thread Id.
id get_id()
giá trị trả về kiểu id được định nghĩa trong Thread class Vd:
//create 3 different threads thread t1(showMessage); thread t2(showMessage); thread t3(showMessage); //get id of all the threads thread::id id1 = t1.get_id(); thread::id id2 = t2.get_id(); thread::id id3 = t3.get_id(); //join all the threads if (t1.joinable()) { t1.join(); cout << "Thread with id " << id1 << " is terminated" << endl; } if (t2.joinable()) { t2.join(); cout << "Thread with id " << id2 << " is terminated" << endl; } if (t3.joinable()) { t3.join(); cout << "Thread with id " << id3 << " is terminated" << endl; }
Mỗi thread sẽ print out unique identifier sau khi kết thúc execution:
Thread with id 8228 is terminated
Thread with id 10948 is terminated
Thread with id 9552 is terminated
this_thread namespace từ thread header cung cấp khả năng làm việc với thread hiện tại. Namespace này chứa 4 chứa năng hữu ích:
-
id_get_id() – trả về id của current thread.
-
template void sleep_until (const chrono::time_point<Clock,Duration>& abs_time) – blocks lại current thread cho đến khi abs_time không đạt được!
-
template void sleep_for (const chrono::duration<Rep,Period>& rel_time); – thread được block lại trong khoảng thời gian quy định bởi rel_time.
-
void yield() – Thread hiện tại cho phép implement để sắp xếp lại việc thưc hiện thread. Nó được sử dụng để tránh blocking.
Vd:
#include <iostream> #include <iomanip> #include <thread> #include <chrono> #include <ctime> using namespace std; using std::chrono::system_clock; int main() { cout << "The id of current thread is " << this_thread::get_id << endl; //sleep while next minute is not reached //get current time time_t timet = system_clock::to_time_t(system_clock::now()); //convert it to tm struct struct tm * time = localtime(&timet); cout << "Current time: " << put_time(time, "%X") << ' '; std::cout << "Waiting for the next minute to begin... "; time->tm_min++; time->tm_sec = 0; //sleep until next minute is not reached this_thread::sleep_until(system_clock::from_time_t(mktime(time))); cout << std::put_time(time, "%X") << " reached! "; //sleep for 5 seconds this_thread::sleep_for(chrono::seconds(5)); //get current time timet = system_clock::to_time_t(system_clock::now()); //convert it to tm struct time = std::localtime(&timet); cout << "Current time: " << put_time(time, "%X") << ' '; }
Bạn sẽ có output dựa trên current time của bạn:
The id of current thread is 009717C6
Current time: 15:28:35
Waiting for the next minute to begin...
15:29:00 reached!
Current time: 15:29:05
Lập trình multithreading phải đối mặt với 1 vấn đề là truy câp đồng thời đến 1 tài nguyên được chia sẻ. Việc tru cập đồng thời đến 1 nguồn tài nguyên sẽ dẫn đến nhiều lỗi cũng như sự hỗn loạn trong chương trình.
Cùng xem ví dụ dưới:
vector<int> vec; void push() { for (int i = 0; i != 10; ++i) { cout << "Push " << i << endl; _sleep(500); vec.push_back(i); } } void pop() { for (int i = 0; i != 10; ++i) { if (vec.size() > 0) { int val = vec.back(); vec.pop_back(); cout << "Pop "<< val << endl; } _sleep(500); } } int main() { //create two threads thread push(push); thread pop(pop); if (push.joinable()) push.join(); if (pop.joinable()) pop.join(); }
Như bạn thấy, ta có 1 biến global vector vec. 2 thread push và pop cùng cố gắng truy cập vào vector này cùng lúc. Thread push cố gắng đẩy 1 element vào vector trong khi thread pop lại cố gắng lấy 1 phần tử từ vector ra. Việc truy cập vào vector không được đồng bộ, Thread đang truy cập vector không liên tục, bởi vì truy cập đồng thời vào dữ liệu được chia sẽ thì nhiều lỗi có thể xuất hiện.
Class mutex laf 1 đồng bộ hoá nguyên thuỷ được sử dụng để bảo vệ được chia sẻ từ các simultaneous access (truy cập cùng lúc). 1 mutex có thể được khoá và mở. Nếu 1 mutex đươcj khoá, thread hiện tại chứa mutex đó cho đến khi nó chưa được unlock trở lại. Nghĩa là không 1 thread nào khác có thể thực hiện bất kỳ instructions từ các khối code được bao quanh bởi mutex cho đến khi thread hiện tại mở nó. Nếu bạn muốn sử dụng mutex, bạn cần include mutex header vào trong chương trình:
#include <mutex>
Sau đó bạn phải tạo 1 biến global kiểu mutex. Nó sẽ được sử dụng để đồng bộ các truy cập vào dữ liệu được chia sẻ: Mỗi khi bạn muốn phần nào của chương trình chỉ được sử dụng bởi 1 thread trong cùng 1 thời gian, bạn sử dụng lock trong mutex:
Đoạn code ở bên trên có thể được sửa như sau:
void push() { m.lock(); for (int i = 0; i != 10; ++i) { cout << "Push " << i << endl; _sleep(500); vec.push_back(i); } m.unlock(); } void pop() { m.lock(); for (int i = 0; i != 10; ++i) { if (vec.size() > 0) { int val = vec.back(); vec.pop_back(); cout << "Pop " << val << endl; } _sleep(500); } m.unlock(); }
Như bạn thấy push và pop đã được khoá bằng mutex. Do đó nếu 1 thread chạy đoạn code được lock bởi mutext, không có thread nào có thể chạy cho đến khi mutex được mở khoá, bạn có thể run đoạn code 1 lần nữa:
//create two threads thread push(push); thread pop(pop); if (push.joinable()) push.join(); if (pop.joinable()) pop.join();
Bây giờ vector đã được đồng bộ hoá:
Push 0
Push 1
Push 2
Push 3
Push 4
Push 5
Push 6
Push 7
Push 8
Push 9
Pop 9
Pop 8
Pop 7
Pop 6
Pop 5
Pop 4
Pop 3
Pop 2
Pop 1
Pop
Chúng ta có thể mô tả bằng 1 ví dụ khác của việc sử dụng mutex, 1 ví dụ về cuộc sống thực:
Rất nhiều người cùng tới 1 box điện thoại công cộng để gọi điện, người nào giữ cửa sẽ là người duy nhất được sử dụng điện thoại. Người đó phải giữ cánh cửa để dử dụng điện thoại, nếu không người khác sẽ mở cửa và đá đít anh ta ra ngoài để chiếm quyền dùng điện thoại