0% found this document useful (0 votes)
56 views

Multi Thread

The document discusses various techniques for multi-threading in C++ including passing functions/lambdas to threads, sharing data between threads safely using atomic/mutex, returning values from threads with promises/futures, signalling between threads with condition variables, and using blocking queues. Key points covered are how to create and manage multiple threads, synchronize shared access to data, and allow threads to communicate with each other.

Uploaded by

Hồng Sơn
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
56 views

Multi Thread

The document discusses various techniques for multi-threading in C++ including passing functions/lambdas to threads, sharing data between threads safely using atomic/mutex, returning values from threads with promises/futures, signalling between threads with condition variables, and using blocking queues. Key points covered are how to create and manage multiple threads, synchronize shared access to data, and allow threads to communicate with each other.

Uploaded by

Hồng Sơn
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

MultiThread

We can pass function pointers, lambda expressions or callable objects.


1) Join
Khi create 1 thread, để thread main không kết thúc trước khi thread mới thực hiện xong, cần Join
EX: thread t1(function);
t1.join(); //main sẽ đợi t1 thực hiện xong function rồi mới kết thúc
#include<chrono>
this_thread::sleep_for(chrono::milliseconds(50));
->Thread với argument là lambda function, normal function,….
Thread([](){code here}); Commented [NHS21]: Indent: thụt vào
Collapse: sự sụp đổ
Atomic: nguyễn tử
2) Share data
Multi thread cùng sử dụng 1 vùng data -> có thể dẫn đến một số problem
➔ Có thể sử dùng atomic<dataType> ((#include <atomic>))
Ex:
atomic<int> count;
thread t1([&count](){
for(int i = 0; i < 1000; i++){
count ++;
}
});
thread t2([&count](){
for(int i = 0; i < 1000; i++){
count ++;
}
});
t1.join();
t2.join();
cout << count;

➔ Có thể sử dụng Mutexs (#include <mutex>)


EX:
int count = 0;
mutex mtx;
auto func = [&count,&mtx](){
for(int i = 0; i < 1000; i++){
mtx.lock();
count ++;
mtx.unlock();
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << count;
3) Thread làm việc với normal function, pass value and pass reference cho hàm như thế nào?
EX:
void func(int& count, mutex& mtx){
for(int i = 0; i < 1000; i++){
mtx.lock();
count ++;
mtx.unlock();
}
}
int main() {
int count = 0;
mutex mtx;
thread t1(func, ref(count), ref(mtx)); //count, mtx là argument của func
thread t2(func, ref(count), ref(mtx)); //ref() dùng để pass reference
thread t1(func, count, mtx); //pass by value
t1.join();
t2.join();
cout << count;
return 0;
}

4) Lock Guards / Unique Lock


it's not actually all that common to use, mutex is because what happens if the code in the
critical section, the bit between lock and unlike what happens if that throws an exception, then
your mutex will never get unlocked.
So for that reason, it's more common to use unique lock or lock guards.
EX:
void func(int& count, mutex& mtx){
for(int i = 0; i < 1000; i++){
lock_guard<mutex> guard(mtx); //call mutex lock
// unique_lock<mutex> guard(mtx);
count ++;
} //will unlock when guard out of scope
}
5) Threading with callable objects

class App{
private:
int count = 0;
mutex mtx;
public:
void operator()(){
cout <<"hello"<<endl;
for(int i = 0; i < 1000; i++){
lock_guard<mutex> guard(mtx); //call mutex lock
// unique_lock<mutex> guard(mtx);
count ++;
} //will unlock when guard out of scope
}
int getCount(){
return count;
}
};

int main()
{
App app; //class App cần overload operator() để có thể pass argument to thread
thread t1(ref(app));
//cần dùng ref() do biến mutex của class App không được phép copy constructor
//khi pass argument là app (pass value) thì sẽ copy constructor ->error -> cần pass reference
ref(app)
thread t2(ref(app)); //ref() dùng để pass reference
t1.join();
t2.join();
cout << app.getCount();
return 0;
}
6) Returning Values from Threads
6.1 Calculator pi
EX: Tính pi : 1 – 1/3 + 1/5 – 1/7 + 1/9 -…..
double calculate_pi(int terms){
double sum = 0.0;
for(int i = 0; I < terms; i++){
int sign = pow(-1,i); //tìm dấu trước mỗi số hạng
double term = 1.0/(i*2+1);
sum += sign * term;
}
return sum*4;
}

6.2 Promises and Futures


Use promises to kind of set a value.(thiết lập 1 giá trị)
We're going to use it to set and get a double type, which is the return value of our function.
promise<dataTypeToSetValue> promise;
So I'm going to include future here and now when the thread runs, it can use the promise to set the
return result, the result that it's going to return to the main threat in this case.
 set the results into the promise now in our main thread, we're able to obtain that value and the
way we do it is via a thing called a future.
future< dataTypeToSetValue > future = promise.get_ future();
//để get được return value dùng lệnh get của future như sau:
cout << future.get();

The future is that is the thing that you used to get the result.
The promise is the thing that you used to set the result.
6.3 Promises and Exceptions
#include <exception>
double calculate_pi(int terms) {
double sum = 0.0;
if (terms < 1) {
throw runtime_error("Terms cannot be less than 1");
}
for (int i = 0; i < terms; i++) {
int sign = pow(-1, i);
double term = 1.0 / (i * 2 + 1);
sum += sign * term;
}
return sum * 4;
}
int main(){
promise<double> promise;
auto do_calculation = [&](int terms) {
try {
auto result = calculate_pi(terms);
promise.set_value(result);
}
catch (...) {
promise.set_exception(current_exception()); //promise set exception
}
};
thread t1(do_calculation, 10000);//10000 is pass argument to function do_calculation
future<double> future = promise.get_future();
try {
cout << setprecision(15) << future.get() << endl;
}
catch (const exception &e) {
cout << e.what() << endl;
}
t1.join();
return 0;
}
6.4 Packaged Tasks
When 6.3 has exception, code is too long -> use packaged
double calculate_pi(int terms)
{
double sum = 0.0;
if (terms < 1){
throw runtime_error("Terms cannot be less than 1");
}
for (int i = 0; i < terms; i++){
int sign = pow(-1, i);
double term = 1.0 / (i * 2 + 1);
sum += sign * term;
}
return sum * 4;
}
int main(){
packaged_task<double(int)> task1(calculate_pi);
// packaged_task<returnTypeOfFunction(dataTypeOfParameter)> task1(nameFunction);
future<double> future1 = task1.get_future();
thread t1(move(task1), 0);
//task1 do not accept to copy constructor -> use ref() or move() (move semantic, unique
pointer)
try {
double result = future1.get();
cout << setprecision(15) << result << endl;
}
catch (exception &e) {
cout << "ERROR! " << e.what() << endl;
}
t1.join();
return 0;
}
//if have exception it will print: ERROR! Terms cannot be less than 1 Commented [NHS22]: Ultimately: cuoi cung
7) Signalling
7.1 Waiting for Threads
int main() {
atomic<bool> ready = false;
thread t1([&](){
this_thread::sleep_for(chrono::milliseconds(2000));
ready = true;
});
t1.join();
while(!ready) {
this_thread::sleep_for(chrono::milliseconds(100));
}
cout << "ready " << ready << endl;
return 0;
}
7.2 Condition Variables (use unique_lock, unlock rồi sau đó notify condition to other thread use this
condition)
#include <condition_variable>
int main() {
condition_variable condition;
mutex mtx;
bool ready = false;
thread t1([&](){
this_thread::sleep_for(chrono::milliseconds(2000));
unique_lock<mutex> lock(mtx);
ready = true;
lock.unlock();
condition.notify_one();
});
unique_lock<mutex> lock(mtx); //argument truyền vào tương tự với trong thread
notify condition
while(!ready) {
condition.wait(lock);
}
cout << "ready " << ready << endl;
t1.join();
return 0;
}
//condition.notifu_one(), condition.notifu_all() ->thông báo đến một hoặc tất cả các thread đang
waiting for condition để wakeup và thực hiện statement
7.3 Checking Condition Shared Resources
Instead of having that loop, what we can do is this the second argument that we can supply to wait,
while(!ready) {
condition.wait(lock);
}
=> condition.wait(lock, [&](){ return ready; }); //return false -> continue wait and reverse
7.4 Blocking Queues
Queue is a first in first out in data structure,
-Tìm hiểu boost blocking queue

7.5 Using Methods in Threads


template<typename E>
class blocking_queue {
public:
void push(E e) {
cout << "push" << endl;
}
void pop() {
cout << "pop" << endl;
}
};
int main() {
blocking_queue<int> qu;
thread t1(&blocking_queue<int>::push, &qu, 7);
// giống QObject::connect(&mPlayer, &QMediaPlayer::durationChanged,…), pass address
argument , address of object and address of method
thread t2(&blocking_queue<int>::pop, &qu);
t1.join();
t2.join();
return 0;
}
7.6 Containers and Thread Safety
#include <queue>
using namespace std;
template<typename E>
class blocking_queue {
private:
int _max_size;
queue<E> _queue;
public:
blocking_queue(int max_size): _max_size(max_size) { }
void push(E e) {
_queue.push(e);
}
E pop() {
E item = _queue.front();
_queue.pop();
return item;
}
};
int main() {
blocking_queue<int> qu(5);
thread t1([&](){
for(int i = 0; i < 10; i++) {
qu.push(i);
}
});
thread t2( [&]() {
for(int i = 0; i < 10; i++) {
auto item = qu.pop();
cout << "consumed " << item << endl;
}
});
t1.join();
t2.join();
return 0;
}
//để sử dụng container trong các thread khác nhau, ví dụ sử dụng queue -> push ở 1 thread và pop ở
1 thread khác có thể dẫn đến crash, sai sót.
7.7 Producer Consumer
#include <queue> #include <mutex> #include <condition_variable>
template<typename E>
class blocking_queue {
private:
mutex _mtx;
condition_variable _cond;
int _max_size;
queue<E> _queue;
public:
blocking_queue(int max_size): _max_size(max_size) { }
void push(E e) {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this](){ return _queue.size() < _max_size; });
_queue.push(e);
lock.unlock();
_cond.notify_one();
}
E pop() {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this](){ return !_queue.empty(); });
E item = _queue.front();
_queue.pop();
lock.unlock();
_cond.notify_one();
return item;
}
int size() {
return _queue.size();
}
};
int main() {
blocking_queue<int> qu(3);
thread t1([&](){
for(int i = 0; i < 10; i++)
{
cout << "pushing " << i << "queue size is " << qu.size() << endl;
qu.push(i);
}
});
thread t2([&](){
for(int i = 0; i < 10; i++)
{
auto item = qu.pop();
cout << "consumed " << item << endl;
}
});
t1.join();
t2.join();
return 0;
}
7.8 A Blocking Queue
template<typename E>
class blocking_queue {
private:
mutex _mtx;
condition_variable _cond;
int _max_size;
queue<E> _queue;
public:
blocking_queue(int max_size): _max_size(max_size){ }
void push(E e) {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this](){ return _queue.size() < _max_size; });
_queue.push(e);
lock.unlock();
_cond.notify_one();
}
E front() {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this](){ return !_queue.empty(); });
return _queue.front();
}
void pop() {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this](){ return !_queue.empty(); });
_queue.pop();
lock.unlock();
_cond.notify_one();
}
int size(){
lock_guard<mutex> lock(_mtx);
//cần lock lại do các thread đều có thể truy cập same time -> không đồng bộ -> sai
return _queue.size();
}
};
int main() {
blocking_queue<int> qu(3);
thread t1([&](){
for(int i = 0; i < 10; i++){
cout << "pushing " << i << "queue size is " << qu.size() << endl;
qu.push(i);
}
});
thread t2([&](){
for(int i = 0; i < 10; i++) {
auto item = qu.front();
qu.pop();
cout << "consumed " << item << endl;
}
});
t1.join(); t2.join();
return 0;
}
8) Processing Work Efficiently
8.1 Async
You must remember to get a future even if you're not planning to get a return value from your
function(bạn phải nhớ get future ngay cả khi you are not planning to get return value from function.)
void work(int id) //int work(int id)
{
for (int i = 0; i < 5; i++) {
cout << "running " << id << endl;
this_thread::sleep_for(chrono::milliseconds(1000));
}
//return id * 7;
}
int main()
{
future<void> f1 = async(launch::async, work, 0);
future<void> f2 = async(launch::async, work, 1);
//future<int> f1 = async(launch::async, work, 0);
//future<int> f2 = async(launch::async, work, 1);
//cout << f1.get() << endl;
//cout << f2.get() << endl;
return 0;
}

Note: async có các thuộc tính quan trọng sau:


- launch::async: cần phải gán future (future<void> f1 = async(launch::async, work, 0);) ngay cả
khi fuction not return value, không cần f1.get() khi hàm not return value, cần get khi hàm return;
-launch::deferred: cần gán future (future<void> f1 = async(launch::async, work, 0);) và cần phải
f1.get() kể cả khi hàm có return value hay không;
8.2 Hardware Concurrency
How many threads we can genuinely(chân thật, thật sự) run at run at the same time.
cout << thread::hardware_concurrency() << endl;
8.3 Launching Lots of Threads
mutex g_mtx;
int work(int id) {
unique_lock<mutex> lock(g_mtx);
cout << "Starting " << id << endl;
lock.unlock();
this_thread::sleep_for(chrono::seconds(3));
return id;
}
int main() {
vector<shared_future<int>> futures;
for(int i = 0; i < thread::hardware_concurrency(); i++) {
shared_future<int> f = async(launch::async, work, i);
futures.push_back(f);
//để có thể copy giá trị future (pass argument…) thì cần dùng share_future
//share_future has copyable ability
}
for(auto f: futures) {
cout << "Returned: " << f.get() << endl;
}
return 0;
}
8.4 A Thread Pool
mutex g_mtx;
template <typename E>
class blocking_queue {
private:
mutex _mtx;
condition_variable _cond;
int _max_size;
queue<E> _queue;
public:
blocking_queue(int max_size) : _max_size(max_size){}
void push(E e) {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this]() { return _queue.size() < _max_size; });
_queue.push(e);
lock.unlock();
_cond.notify_one();
}
E front() {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this]() { return !_queue.empty(); });
return _queue.front();
}
void pop() {
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this]() { return !_queue.empty(); });
_queue.pop();
lock.unlock();
_cond.notify_one();
}
int size() {
lock_guard<mutex> lock(_mtx);
return _queue.size();
}
};
int work(int id){
unique_lock<mutex> lock(g_mtx);
cout << "Starting " << id << endl;
lock.unlock();
int seconds = int((5.0 * rand()) / RAND_MAX + 3);
this_thread::sleep_for(chrono::seconds(seconds));
return id;
}
int main() {
// Here the argument supplied to the queue needs to be
// one less than the number of threads you want to launch.
blocking_queue<shared_future<int>> futures(2);
thread t([&]() {
for (int i = 0; i < 20; i++) {
shared_future<int> f = async(launch::async, work, i);
futures.push(f);
}
});
for (int i = 0; i < 20; i++) {
shared_future<int> f = futures.front();
int value = f.get();
futures.pop();
cout << "Returned: " << value << endl;
}
t.join();
return 0;
}
8.5 Distributing Work Between Cores
double calculate_pi(int terms, int start, int skip)
{
double sum = 0.0;
for (int i = start; i < terms; i += skip)
{
int sign = pow(-1, i);
double term = 1.0 / (i * 2 + 1);
sum += sign * term;
}
return sum * 4;
}
int main()
{
vector<shared_future<double>> futures;
const int CONCURRENCY = thread::hardware_concurrency();
for (int i = 0; i < CONCURRENCY; i++)
{
shared_future<double> f = async(launch::async, calculate_pi, 1E7, i, CONCURRENCY);
futures.push_back(f);
}
double sum = 0.0;
for (auto f : futures)
{
sum += f.get();
}
cout << setprecision(15) << "PI: " << M_PI << endl;
cout << setprecision(15) << "Sum: " << sum << endl;

return 0;
}
8.6 Timing Code

auto start = chrono::steady_clock::now();


for (int i = 0; i < CONCURRENCY; i++)
{
shared_future<double> f = async(launch::async, calculate_pi, 1E8, i, CONCURRENCY);
futures.push_back(f);
}
double sum = 0.0;
for (auto f : futures)
{
sum += f.get();
}
auto end = chrono::steady_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start).count();
cout << "Duration: " << duration << endl;

giả sử CONCURRENCY = 6; 6 thread chạy song song


-> for (int i = 0; i < CONCURRENCY; i++) -> start 6 thread với 6 giá trị với argument là
start = i->CONCURRENCY, skip = CONCURRENCY
thread1: start = 0, skip = 6 -> 0, 6, 12,…
thread2: start = 1, skip = 6 -> 1, 7, 13,…
thread3: start = 2, skip = 6 -> 2, 8, 14,…
thread4: start = 3, skip = 6 -> 3, 9, 15,…
thread5: start = 4, skip = 6 -> 4, 10, 16,…
thread6: start = 5, skip = 6 -> 5, 11, 17,…
➔ Mỗi thread chạy với 1 chỉ số khác nhau chung quy tất cả các thread sẽ chạy từ 0 -> terms
Tương đương với for (int i = 0; i < terms ; i ++)
QT: MultiThread and MultiProcess
1.Process
- Processes don't share the resources allocated by the operating system.
- There are clear boundaries between processes and it is really difficult to communicate
between processes.
- A process is a running application on your system.
- Each process can have one or multiple threads and threads kind of overload heavy work from
the main thread and this allows for your application not to block.

2.Thread
-Each process can have multiThread
- Thread's share the resources that are allocated to the process.
- Another benefit of using threads is that they allow you to take advantage of multicore
processors.
- When you have multiple threads, it is possible for each thread to be running on a different
core and this is going to speed things up for your application.
QT Thread: Qt Concurrent, Qt ThreadPool, QThread
QThread:
- QThread::create: can use create method from Q thread class
- moveToThread: can move the objects to another thread
- Subclass QThread: can subclass QThread to create our own thread

1) QThread::create
- QThread::create does not give you event loop:
=> do not have loop to catch signals from User’s actions
=> emit signal finished when method returns
=> Any calls to isRunning() after method returns will return false
- Remember manage memory when create thread:
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
- Trong khi bắt 1 signal, nếu trong slot đó thực hiện một hành động quá lâu sẽ dẫn
đến block UI ->đưa action đó vào 1 thread khác để thực hiện
EX:
void counting1(int count){
for(int i{0} ; i < count ; i ++){
qDebug() << "Counting : " << i << "thread : " << QThread::currentThread();
}
}
void Widget::counting(){
for(int i{0} ; i < 10000 ; i ++){
qDebug() << "Counting method called : " << i <<
" thread :" << QThread::currentThread() << " id : " <<
QThread::currentThreadId() <<
"Thread is running : " << thread->isRunning();
}
}
void Widget::on_startButton_clicked(){
/*
//0.Freeze the UI
for(int i{0} ; i < 1000000 ; i ++){
qDebug() << " counting in ui thread..."<< "Counting : " << i <<
" thread :" << QThread::currentThread() << " id : " <<
QThread::currentThreadId();
}
//1 .Global function
/* thread = QThread::create(counting1,10000); */

//2.Named lambda function


/*
auto countlambda = [](int count){
for(int i{0} ; i < count ; i ++){
qDebug() << "countlambda counting..."<< "Counting : " << i <<
" thread :" << QThread::currentThread() << " id : " <<
QThread::currentThreadId();
}
};
thread = QThread::create(countlambda,1000000);
*/
//3. Non named lambda function
thread = QThread::create([](){
for(int i{0} ; i < 100000 ; i ++){
qDebug() << "Counting : " << i <<
" thread :" << QThread::currentThread() << " id : " <<
QThread::currentThreadId();
}
});

//4.Member function, call from lamba function


thread = QThread::create([=](){
counting(); //member function
});
connect(thread,&QThread::started,[](){
qDebug() << "Thread started";
});
connect(thread,&QThread::finished,[](){
qDebug() << "Thread finished";
});

connect(thread,&QThread::finished,thread,&QThread::deleteLater);
thread->start();
qDebug() << "Clicked on the start button";
}
2) moveToThread

Link: https://github.com/rutura/ThreadingIPCCode/tree/master/2.CreatingThreads/2-
3MoveToThread
Note: Give us an event loop by default
- Action done but thread is still running because there is an event loop running in the
thread
- To force the even loop, call the exit method on the thread
Second thread doCounting() đếm đến số cực lớn, for(int i(0), i < 1E8, i++), mỗi giá trị của i
sẽ emit currentCount(int value) sang mainThread nhận value và hiển thị lên UI
Nhưng số thông tin được emit sang mainThread quá lớn có thể dẫn đến block UI -> chia
nhỏ khoảng value để emit, cứ sau mỗi 10k đơn vị thì emit 1 lần -> số lần emit sẽ nhỏ ->
không block UI

QThread * workerThread; //in widget.h


void Widget::on_startButton_clicked()
{
Worker * worker = new Worker; //Main thread, class Worker

worker->moveToThread(workerThread); //cần kế thứ QObject

connect(workerThread, &QThread::started, worker, &Worker::doCounting);


connect(worker, &Worker::currentCount, this, &Widget::currentCount);
connect(worker, &Worker::countDone, this, &Widget::countDone);
connect(workerThread, &QThread::finished, worker, &Worker::deleteLater);

workerThread->start();
}
3) Subclass QThread
Link: https://github.com/rutura/ThreadingIPCCode/tree/master/2.CreatingThreads/2-
4SubclassQThread
Note: - Don’t give us an event loop by default
- Create class derived from QThread class, you can create class derived from
QObject and then replace QObject by QThread
- We want to implement a method from the interface of thread, the way you do that,
which is pretty easy, is refactored.
- You right click on the class name here, you select refactor and you choose insert
virtual functions of base classes -> choose method
=>Method run() of QThread can give us an event loop here when we add statement exec();
EX:
WorkerThread * workerThread; // WorkerThread : public QThread
void WorkerThread::run() {
qDebug() << "Run method in thread : " << QThread::currentThread();
for(int i{0} ; i < 1000000001 ; i++){
/ * Only emit signal to send info to ui at 100000 intervals. UI can handle this.
* Otherwise it is going to freeze.*/
if((i%100000) == 0){
double percentage = ((i/1000000000.0)) * 100;
emit currentCount(QVariant::fromValue(percentage).toInt());
}
}
//Start event loop
exec() ;
}
//constructor class Widget
Widget::Widget(QWidget *parent) :QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::currentCount, this, &Widget::currentCount);

connect(workerThread,&QThread::started,[](){
qDebug() << "Thread started";
});

connect(workerThread,&QThread::finished,[](){
qDebug() << "Thread finished";
});
}
void Widget::on_startButton_clicked() {
workerThread->start();
}

void Widget::currentCount(int value){


ui->progressBar->setValue(value);
ui->infoLabel->setText(QString::number(value));
}

4) QThread with asynchronous code-QThread-Create

You might also like