Топ контрибуторов
loading
loading
Знаете ли Вы, что

Если у вас есть уникальная статья и вы хотите, чтобы она стала достоянием общественности, вы можете разместить ее на Quizful.

Лента обновлений
ссылка May 19 16:30
Комментарий от Rozrabiaka:
Да тест очень хорош!!!
ссылка May 19 16:29
Добавлен вопрос в тест HTML - Средний уровень
ссылка May 18 13:25
Комментарий от MAX200688:
А как же неявный вызов конструктора родительского клас...
ссылка May 17 20:56
Комментарий от motya:
И лишнюю точку с запятой поставить, чтобы народ сетовал
ссылка May 17 20:49
Комментарий от motya:
Программы не существует
Статистика

Тестов: 153, вопросов: 8595. Пройдено: 433551 / 2126505.

Многопоточность, новые возможности стандарта C++11

head tail Статья
категория
C++
дата22.10.2013
авторnarekvar90
голосов16

Вступление

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

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

    В новом стандарте 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, 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<cstdlib>
#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;
}

Одновременный доступ к ресурсам


    В вышеприведенном примере для печати на экран я использовал sstream, предварительно превращая в строку то что хочу печатать а потом передаю в поток вывода. А что если сразу передать в поток вывода, сперва первую строку, потом переменную типа thread::id и наконец вторую строку. Но в этом случае потоки не по очереди будут передавать в поток вывода и в итога получается совсем не то что мы хотели. Такая ситуация бывает когда несколько потоков работают с одним и тем же объектом. Для того чтобы предотвратить это воспользуемся классом mutex который определен в файле mutex. Переменная типа mutex можно блокировать и разблокировать. Когда вызывается метод lock класса mutex, метод проверяет объект и если он разблокирован то блокирует его и возвращает выполнение, в противном случае оно ждет пока объект разблокируется и после чего делает то же самое. А метод unlock разблокирует объект класса mutex этим позволяя другим процессам его блокировать.
#include<iostream>
#include<cstdlib>
#include<vector>
#include<mutex>
#include<thread>
using namespace std;

const int elementsCount = 10;

void push(vector<int> &arr, mutex& m_arr, mutex& m_out) {
    int num;
    for (int i = 0; i < elementsCount; i++) {
        m_arr.lock();
        num = rand();
        arr.push_back(num);
        m_arr.unlock();
        m_out.lock();
        cout << "Push " << num << "\n";
        m_out.unlock();
    }
}

void pop(vector<int> &arr, mutex& m_arr, mutex& m_out) {
    int i = 0, num;
    while (i < elementsCount) {
        m_arr.lock();
        if (arr.size() > 0) {
            num = arr.back();
            arr.pop_back();
            m_out.lock();
            cout << "Pop " << num << "\n";
            m_out.unlock();
            i++;
        }
        m_arr.unlock();
    }
}

int main() {
    mutex m_arr, m_out;
    vector<int> vec;
    thread push_thread(push, ref(vec), ref(m_arr), ref(m_out));
    thread pop_thread(pop, ref(vec), ref(m_arr), ref(m_out));
    if (push_thread.joinable()) push_thread.join();
    if (pop_thread.joinable()) pop_thread.join();
    return 0;
}
В вышеприведенной программе мы создаем два объекта класса thread первый инициализируем функцией push, который добавляет в вектор 10 элементов блокируя и разблокируя объекты m_arr и m_out, а второй функцией pop, который удаляет 10 элементов из вектора, конечно опять блокируя и разблокируя объекты m_arr и m_out.
    Источник http://www.cplusplus.com/reference/multithreading/.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 16  loading...
narekvar90   Aperovich   ilia   Yar   Lepsik   shadezx   dendi   teinnsei   tamirladi   PilOfSpar   PauloMaldini   olgerd27   Instand   FrozenTwilight   elenalizina   ppanchen