Многопоточное программирование, thread-safety 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Многопоточное программирование, thread-safety



15.1. ВВЕДЕНИЕ В МНОГОПОТОЧНОСТЬ. НОВЫЕ ВОЗМОЖНОСТИ СТАНДАРТА C++11

http://ru.cppreference.com/w/cpp/thread

Многопоточность в C++11 - YouTube (http://www.youtube.com/watch?v=TA7M9Ojje-A)

Процессом (process) называется экземпляр программы, загруженной в память. Этот экземпляр может создавать нити (thread), которые представляют собой последовательность инструкций на выполнение. Важно понимать, что выполняются не процессы, а именно нити.

Причем любой процесс имеет хотя бы одну нить. Эта нить называется главной (основной) нитью приложения.

Потоки предоставляют возможность проведения параллельных или псевдопараллельных, в случае одного процессора, вычислений. Потоки могут порождаться во время работы программы, процесса или другого потока. Основное отличие потоков от процессов заключается в том, что различные потоки имеют различные пути выполнения, но при этом пользуются общей памятью. Путь выполнения потока задается при его создании, указанием его стартовой функции, созданный поток начинает выполнять команды этой функции, и завершается когда происходит возврат из функции.

Так как практически всегда нитей гораздо больше, чем физических процессоров для их выполнения, то нити на самом деле выполняются не одновременно, а по очереди (распределение процессорного времени происходит именно между нитями). Но переключение между ними происходит так часто, что кажется, будто они выполняются параллельно.

 

Инициализация потока

В новом стандарте C++11 многопоточность осуществлен в классе thread, который определен в файле thread.h. Для того чтобы создать новый поток нужно создать объект класса thread и инициализировать передав в конструктор имя функции которая должна выполнятся в потоке. Давайте посмотрим на маленький пример многопоточной программы, чтобы все стало понятнее.

#include<iostream>

#include<thread> //Файл в котором определен класс thread

using namespace std;

void anyFunc() {

cout<<"thread function";

}

int main() {

thread func_thread(anyFunc);

return 0;

}

В этой программе создается новый объект класса thread и в объявлении конструктору передается имя функции anyFunc, который печатает на экране текст "thread function". Но если скомпилировать данное приложение и запустить, то как бы странно не было оно закончится аварийна с сообщением об ошибке. Все дело в том что главная функция программы main создает объект func_thread, с параметром конструктора anyFunc и продолжает свое выполнение не дожидаясь чтобы процесс закончился, что и вызывает ошибку времени выполнения. Чтобы ошибки не было надо чтобы до того как закончится функция main все потоки были закончены. Это осуществляется путем синхронизации потоков вызывая метод join. Метод join возвращает выполнение программе когда поток заканчивается, после чего объект класса thread можно безопасно уничтожить.

#include<iostream>

#include<thread> //Файл в котором определен класс thread

using namespace std;

void anyFunc() {

cout << "thread function";

}

int main() {

thread func_thread(anyFunc);

func_thread.join();

// Выполнение возвращается функции main когда поток заканчивается

return 0;

}

Позвольте добавить что перед вызовом функции join надо проверить является ли объект joinable то есть представляет он реальный поток или нет, к примеру объект может быть объявлен но не инициализирован или уже закончен вызовом функции join. Проверка делается функцией joinable, который возвращает true в случае если объект представляет исполняемый поток и false в противном случаи. Надо отметить что может быть ситуация когда нам ненужно ждать чтобы поток закончился, для этого у класса thread есть другой метод по имени detach. Обе метода ничего не принимают и не возвращают и после их вызова объект становится not joinable и можно безопасно уничтожить.

#include<iostream>

#include<thread> //Файл в котором определен класс thread

using namespace std;

 

void anyFunc() {

cout << "thread function";

}

 

int main() {

thread func_thread(anyFunc);

if (func_thread.joinable())

func_thread.join();

// Выполнение возвращается функции main когда поток заканчивается

// func_thread.detach(); В этом случае поток заканчивается принудительно

return 0;

}

 

В инициализацию объекта можно и передать параметры в функции перечисляя их после имени функции как продемонстрировано в следующем примере:

 

#include<iostream>

#include<thread> //Файл в котором определен класс thread

using namespace std;

 

void printStr(char * str) {

cout << str << '\n';

}

 

void printArray(int a[],const int len) {

for (int i = 0; i < len; i++) {

cout << a[i] << ' ';

}

}

 

int main() {

char* str = "thread function with parametrs";

const int len = 8;

int arr[len] = {12, 45, -34, 57, 678, 89, 0, 1};

// Передаем параметр функции во время инициализации

thread func_thread(printStr, str);

// Параметров может быть много

thread func_thread2(printArray, arr, len);

if (func_thread.joinable()) func_thread.join();

if (func_thread2.joinable()) func_thread2.join();

return 0;

}

 

Параметры можно передавать не только по значению но и по ссылке. Для чего воспользуемся функцией ref из пространства имен std. В ниже приведенной программе вызываемому потоку передается массив и его длина, а в потоке в него добавляются 5 новых значений.

 

#include<iostream>

#include<cstdlib>

#include<thread>

using namespace std;

 

void addElements(int a[], int &len) {

for (int i = len; i < len + 5; i++) {

a[i] = rand();

}

len += 5;

}

 

int main() {

const int LENGTH = 20;

int arr[LENGTH] = {1, 2, 3, 4, 5}, current_length = 5;

cout << "Output the array before thread\n";

for (int i = 0; i < current_length; i++) {

cout << arr[i] << ' ';

}

thread arr_thread(addElements, arr, ref(current_length));

if (arr_thread.joinable()) arr_thread.join();

cout << "\nOutput th array after thread\n";

for (int i = 0; i < current_length; i++) {

cout << arr[i] << ' ';

}

return 0;

}

Потоки могут быть инициализированны не только функцией но и объект функцией, то есть в классе объекта определен метод operator(), и обычным открытым методом класса. В первом случае нужно передать объект этого класса в конструктор thread, а во втором случае ссылку на функцию и адрес объекта, конечно не надо забыть про список параметров если они есть.

#include<iostream>

#include<thread>

using namespace std;

 

class arrayModifier {

public:

void operator()(int a[], int len) {

for (int i = 0; i < len; i++) {

a[i] *= 2;

}

}

void invers(int a[], int len) {

for (int i = 0; i < len; i++) {

a[i] *= -1;

}

}

};

 

int main() {

const int length = 5;

int arr[length] = {1, 2, 3, 4, 5};

arrayModifier obj;

cout << "Output the array before threads\n";

for (int i = 0; i < length; i++) {

cout << arr[i] << ' ';

}

// Инициализируется объект функцией

thread arr_thread(obj, arr, length);

// Инициализируется обычным открытым методом

thread arr_thread2(&arrayModifier::invers, &obj, arr, length);

if (arr_thread.joinable()) arr_thread.join();

if (arr_thread2.joinable()) arr_thread2.join();

cout << "\nOutput th array after threads\n";

for (int i = 0; i < length; i++) {

cout << arr[i] << ' ';

}

return 0;

}

В вышеприведенном примере у класса arrayModifier есть два метода первый это operator() который элементы массива умножает на 2, а второй умножает на -1. Во втором объекте мы передаем адрес объекта связи с тем что метод класса в качестве скриптого параметра принимает адрес объекта для которого был вызван этот метод.

ID потока

У каждого потока есть свой уникальный номер который отличается от других потоков этой программы. Для этого в классе thread есть закрытый член id и открытый метод get_id вызов которого возвращает значение этого члена.

#include<iostream>

#include<thread>

using namespace std;

 

class printNumber {

public:

void operator()(int number,int arr[],int idx) {

int sum = 0;

for(int i = 0; i < number; i++) {

if(1%15 == 0) continue;

if(i%3 == 0) sum += 3*i;

if(i%5 == 0) sum += 5*i;

}

arr[idx] = sum;

}

};

 

int main() {

const int length = 10;

thread::id id;

thread thread_array[length];

int res_arr[length] = {0};

for (int i = 0; i < length; i++) {

thread_array[i] = thread(printNumber(), rand(),res_arr,i);

}

for (int i = 0; i < length; i++) {

if (thread_array[i].joinable()) {

id = thread_array[i].get_id();

thread_array[i].join();

cout << "Thread with id " << id << " finished. With result "<<res_arr[i]<<"\n";

}

}

return 0;

}

Пространство имен this_thread

В заголовочном файле thread.h определено пространство имен this_thread который содержит в себе функции для работы с конкретным потоком. Три из этих функций для того чтобы на некоторое время остановить выполнение потока: sleep_until - передается переменная класса chrono:time_point и блокируется выполнение потока пока системные часы не дойдут до этого времени; sleep_for - передается переменная класса chrono::duration и выполнение потока блокируется пока не прошло столько времени сколько было преданно; yield - останавливает выполнение потока на некоторое время предоставляя возможность выполнится другим потокам. А четвертая функция это get_id и как метод класса thread возвращает id потока.

#include<iostream>

#include<sstream>

#include<chrono>

#include<thread>

using namespace std;

 

class printNumber {

public:

 

void operator()() {

ostringstream out;

thread::id id = this_thread::get_id();

out << "Thread with id " << id << " started\n";

cout << out.str();

// Останавливает выполнение на одну секунду

this_thread::sleep_for(chrono::seconds(1));

out.str("");

out << "Thread with id " << id << " finished\n";

cout << out.str();

}

};

 

int main() {

const int length = 10;

thread thread_array[length];

for (int i = 0; i < length; i++) {

thread_array[i] = thread(printNumber());

}

for (int i = 0; i < length; i++) {

if (thread_array[i].joinable()) {

thread_array[i].join();

}

}

return 0;

}

 



Поделиться:


Последнее изменение этой страницы: 2016-12-11; просмотров: 319; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 52.15.59.163 (0.035 с.)