C ++ Çox işləmə

Sistem dizaynı ilə bağlı müsahibə sualları o qədər açıq ola bilər ki, düzgün hazırlaşmağı bilmək çox çətindir. İndi satın aldıqdan sonra Amazon, Microsoft və Adobe-nin dizayn dövrlərini sındıra bilirəm Bu kitabı. Gündəlik bir yenidən nəzərdən keçirin dizayn sualı və söz verirəm ki, dizayn dövrünü sındıra bilərsiniz.

C ++ dilində çox işləmə

Çox işləmə nədir?

Çox işləmə bir platformanın (Əməliyyat Sistemi, Virtual Maşın və s.) Və ya tətbiqin çoxdan ibarət olan bir proses yaratmaq qabiliyyətidir. icra mövzuları (iplər). A sap icrası bir planlaşdırıcı tərəfindən müstəqil şəkildə idarə oluna bilən proqramlaşdırma təlimatlarının ən kiçik ardıcıllığıdır. Bu iplər paralel işləyə bilər və proqramların effektivliyini artıra bilər.

Çox nüvəli və çoxsaylı prosessor sistemlərində çox işləmə, fərqli nüvələrin və ya prosessorlarda eyni vaxtda fərqli mövzuların yerinə yetirilməsi deməkdir.

Tək nüvəli sistemlər üçün çox oxlu iplər arasında vaxtı bölür. Əməliyyat sistemi öz növbəsində hər ipdən prosessora müəyyən sayda təlimat göndərir. Mövzular eyni vaxtda yerinə yetirilmir. Əməliyyat sistemi yalnız onların eyni vaxtda icrasını simulyasiya edir. Əməliyyat sisteminin bu xüsusiyyəti multithreading adlanır.

Çox işləmə, bəzi tapşırıqların paralel icrası sistemin resurslarının daha səmərəli istifadəsinə səbəb olduqda istifadə olunur.

Çox işləmə üçün dəstək olaraq C ++ 11-də təqdim edildi. Başlıq faylı iplik.h çox iş parçalı C ++ proqramları yaratmaq üçün funksionallıq təmin edir.

Bir mövzu necə yaradılır?

Əvvəlcə proqramınıza mövzu başlığı əlavə etməlisiniz:

#include <thread>

Bir mövzu yaratmaq istədiyiniz zaman a-nın bir obyekti yaratmalısınız sap sinif.
//this thread does not represent any thread of execution
thread t_empty;

Gördüyünüz kimi, mövzu sinifinin standart qurucusu istifadə edildikdə, mövzuya heç bir məlumat ötürmürük. Bu o deməkdir ki, bu mövzuda heç bir şey icra edilmir. Bir mövzu işə salmalıyıq. Müxtəlif yollarla edilə bilər.

Mövzu bir funksiya ilə işə salınır

Bir mövzu yaratdıqda, bir işarə göstəricisini konstruktoruna ötürə bilərsiniz. Mövzu yaradıldıqdan sonra bu funksiya ayrı bir mövzuda işinə başlayır. Bir nümunəyə baxın:

#include <iostream>
#include <thread> 
using namespace std;
void threadFunc()
{
	cout << "Welcome to Multithreading" << endl;
}
int main()
{
	//pass a function to thread
	thread funcTest1(threadFunc);
}

Bu proqramı tərtib etməyə və işləməyə çalışın. Heç bir səhv olmadan tərtib olunur, ancaq bir iş vaxtı səhvini alacaqsınız:

Gördüyünüz kimi, əsas iplik yeni mövzu yaradır funksiya testi1 bir parametr ilə threadFunc. Əsas mövzu gözləmir funksiya testi1 mövzu dayandırılması. İşini davam etdirir. Əsas iplik icranı bitirir, amma funksiya testi1 hələ də işləyir. Bu səhvə səbəb olur. Əsas iplik sona çatmadan bütün iplər sona çatmalıdır.

Mövzulara qoşulun

Mövzuya qoşulma istifadə edərək edilir qoşulmaq () bir mövzu sinifinin üzv funksiyası:

void join();

Bu funksiya yalnız bütün mövzuların sona çatmasından sonra qayıdır. Bu o deməkdir ki, əsas iplik uşaq ipliyi icrasını bitirməyincə gözləyəcək:

Əvvəlki nümunədə yaradılan mövzu üçün join () çağırın və proqramı yenidən işə salın:

//pass a function to thread
thread funcTest1(threadFunc);
//main is blocked until funcTest1 is not finished
funcTest1.join();

Gördüyünüz kimi, indi proqram uğurla icra olunur.

Qoşula bilən və Qoşulmayan iplər

Join () qayıtdıqdan sonra mövzu olur qoşula bilməz. Birləşdirilə bilən iplik hələ birləşdirilməmiş bir icra ipliyini təmsil edən bir ipdir.

Bir mövzu, əvvəlcədən qurulduqda və ya başqa bir mövzuya köçürüldükdə / təyin edildikdə və ya birləşdirmə () və ya ayırmaq () üzvü funksiyası deyildikdə birləşdirilə bilməz.

Qoşulmayan iplik təhlükəsiz şəkildə məhv edilə bilər.

Birləşdirilə bilən () üzv funksiyasından istifadə edərək bir mövzuya qoşulma olub olmadığını yoxlaya bilərsiniz:

bool joinable()

Mövzu qoşula bilən və əks halda yalan olduqda, bu funksiya doğrudur. Join () funksiyası deyilməzdən əvvəl ipin birləşdirilə biləcəyini yoxlamaq daha yaxşıdır:
//pass a function to thread
thread funcTest1(threadFunc);
//check if thread is joinable
if (funcTest1.joinable())
{
	//main is blocked until funcTest1 is not finished
	funcTest1.join();
}

Mövzu ayırmaq

Yuxarıda qeyd etdiyimiz kimi, iplik birləşdirildikdən sonra olur ayırmaq() üzv funksiyası adlanır:

void detach()

Bu funksiya bir ipliyi ana ipdən ayırır. Valideyn və uşaq mövzularının bir-birindən müstəqil şəkildə yerinə yetirilməsinə imkan verir. Detach () funksiyasının çağrılmasından sonra iplər heç bir şəkildə sinxronizasiya edilmir:
//detach funcTest1 from main thread
funcTest1.detach();
if (funcTest1.joinable())
{
	//main is blocked until funcTest1 is not finished
	funcTest1.join();
}
else
{
	cout << "functTest1 is detached" << endl;
}

Əsas ipliyin uşaq ipinin sona çatmasını gözləmədiyini görəcəksiniz.

Bir obyekt ilə mövzu başlanır

Bir mövzuya yalnız bir funksiya ilə başlanğıc edə bilərsiniz. Bu məqsəd üçün funksiya obyektini (funksiya) və ya bir sinifin üzv funksiyasını istifadə edə bilərsiniz.

Funktor, operatoru həddindən artıq yükləyən bir sinif obyektidir () - funksiya zəng operatoru.

Bir mövzu obyektini bir mövzu ilə işə salmaq istəyirsinizsə, bu sinif operatoru () həddindən artıq yükləməlidir. Bunu aşağıdakı şəkildə etmək olar:

class myFunctor
{
public:
	void operator()()
	{
		cout << "This is my function object" << endl;
	}
};

İndi sinifin bir obyektini ötürərək bir mövzuya başlanğıc edə bilərsiniz myFunctor bir mövzu qurucusuna:
myFunctor myFunc;
thread functorTest(myFunc);
if (functorTest.joinable())
functorTest.join();

Bir sinifin bir ictimai üzv funksiyasına sahib bir mövzu başlatmaq istəyirsinizsə, bu funksiyanın identifikatorunu təyin etməli və bu üzv funksiyanı təyin edən bir sinif obyektini keçməlisiniz:

Bir ictimai üzv funksiyası əlavə edin myFunctor sinif:

void publicFunction()
{
	cout << "public function of myFunctor class is called" << endl;
}

İndi mövzu ilə başlanğıc edə bilərsiniz publicFunction () of myFunctor sinif:
myFunctor myFunc;
//initializing thread with member function of myFunctor class
thread functorTest(&myFunctor::publicFunction,myFunc);
if (functorTest.joinable())
	functorTest.join();

Dəlilləri mövzuya ötürmək

Əvvəlki nümunələrdə bu funksiyalara və obyektlərə heç bir dəlil ötürmədən yalnız funksiyalardan və obyektlərdən istifadə etdik.

Mövzu başlatma üçün parametrləri olan bir funksiyadan istifadə edə bilərik. Bu ehtimalı yoxlamaq üçün yeni bir funksiya yaradın:

void printSomeValues(int val, char* str, double dval)
{
	cout << val << " " << str <<" " << dval << endl;
}

Gördüyünüz kimi, bu funksiya üç arqument tələb edir. Bu funksiya ilə bir mövzu işə salmaq istəyirsinizsə, əvvəlcə bu funksiyaya bir göstərici ötürməlisiniz, sonra arqumentləri funksiyanın parametrlər siyahısında olduğu kimi ardıcıllıqla funksiyaya ötürün:
char* str = "Hello";
//5, str and 3.2 are passed to printSomeValues function
thread paramPass(printSomeValues, 5, str, 3.2);
if (paramPass.joinable())
paramPass.join();

Parametrləri olan bir obyekt ilə bir mövzu işə salmaq istədikdə, operatorun () yüklənmə versiyasına müvafiq parametrlər siyahısını əlavə etməliyik:
class myFunctorParam
{
public:
	void operator()(int* arr, int length)
	{
		cout << "An array of length " << length << "is passed to thread" << endl;
		for (int i = 0; i != length; ++i)
			cout << arr[i] << " " << endl;
		cout << endl;
	}
};

Gördüyünüz kimi operator () iki parametr götürür:
void operator()(int* arr, int length)

Bu vəziyyətdə bir obyekt ilə mövzu başlanğıcı parametrləri olan bir funksiyanı istifadə etməyə bənzəyir:
//these parameters will be passed to thread
int arr[5] = { 1, 3, 5, 7, 9 };
myFunctorParam objParamPass;
thread test(objParamPass, arr, 5);
if (test.joinable())
	test.join();

Parametrləri mövzuya ötürmək üçün bir sinifin üzv funksiyasından istifadə etmək mümkündür. MyFunctorParam sinfinə yeni ictimai funksiya əlavə edin:
void changeSign(int* arr, int length)
{
	cout << "An arrray of length " << length << "is passed to thread" << endl;
	for (int i = 0; i != length; ++i)
		cout << arr[i] << " ";
	cout << "Changing sign of all elements of initial array" << endl;
	for (int i = 0; i != length; ++i)
	{
		arr[i] *= -1;
		cout << arr[i] << " ";
	}
}

Üzv funksiyasına arqumentlərin ötürülməsi:
int arr2[5] = { -1, 3, 5, -7, 0 };
//initialize thread with member function
thread test2(&myFunctorParam::changeSign, &objParamPass, arr2, 5);
if (test2.joinable())
	test2.join();

Bir sinifin üzv funksiyasına arqumentlər ötürdüyünüzdə, arqumentləri funksiyanın parametr siyahısında göstərildiyi qaydada göstərməlisiniz. Mövzu konstruktorunun ikinci parametrindən sonra edilir:
thread test2(&myFunctorParam::changeSign, &objParamPass, arr2, 5);

Mövzu şəxsiyyəti

Hər bir ipin özünəməxsus identifikatoru var. Sinif iş parçasının identifikatorunu qaytaran ümumi üzv funksiyası var:

id get_id()

Qaytarılan dəyər, iş parçası sinfində göstərilən id tipidir.

Aşağıdakı misala baxın:

//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;
}

İcra bitdikdən sonra hər mövzu öz unikal identifikatorunu çap edir:

8228 nömrəli mövzuya xitam verilir
10948 nömrəli mövzuya xitam verilir
9552 nömrəli mövzuya xitam verilir

this_thread ad məkanı

mövzu başlığından this_thread ad sahəsi mövcud mövzu ilə işləmə imkanları təqdim edir. Bu ad sahəsi dörd faydalı funksiyanı ehtiva edir:

1. id_get_id () - cari ipliyin id-ini qaytarır.

2. şablon
void sleep_until (const chrono :: time_point & abs_time) - abs_time əldə olunana qədər cari mövzuları bloklayır.

3. şablon
void sleep_for (const chrono :: müddəti & rel_time); - rel_time tərəfindən göstərilən müddət ərzində mövzu bloklanır.

4. boş məhsul () - cari iplik tətbiqin ipliyin icrasını yenidən planlaşdırmasına imkan verir. Bloklanmamaq üçün istifadə olunurdu.

Bu, bu funksiyaları istifadə etmək üçün bir nümunədir:

#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") << '\n';
	std::cout << "Waiting for the next minute to begin...\n";
	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!\n";
	//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") << '\n';
}

Cari vaxtınıza görə bir nəticə əldə edəcəksiniz:

Cari ipliyin id-si 009717C6-dır
Cari vaxt: 15:28:35
Növbəti dəqiqənin başlayacağını gözləyirəm ...
15:29:00 çatıldı!
Cari vaxt: 15:29:05

Resurslara paralel giriş

Çox işləməli proqramlaşdırma, paylaşılan bir mənbəyə eyni vaxtda giriş problemi ilə qarşılaşır. Eyni mənbəyə eyni vaxtda giriş proqramda bir çox səhv və xaosa səbəb ola bilər.

Aşağıdakı nümunəyə nəzər yetirin:

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();
}

Gördüyünüz kimi qlobal bir vektor var vec tam dəyərlər. İki mövzu basmaq pop eyni vaxtda bu vektora daxil olmağa çalışın: birinci mövzu bir elementi vektora itələyir, ikincisi isə bir elementi vektordan çıxarmağa çalışır.

Vektora giriş sinxronizasiya edilmir. Mövzular fasiləsiz olaraq vektora daxil olur. Paylaşılan məlumatlara eyni vaxtda daxil olma səbəbi ilə bir çox səhv ortaya çıxa bilər.

Muteks

Sinif mutex paylaşılan məlumatları eyni vaxtda girişdən qorumaq üçün istifadə olunan bir sinxronizasiya ibtidasıdır. Mutex kilidlənə və kiliddən çıxarıla bilər. Mutex kilidləndikdən sonra cari iplik açılmayana qədər muteksə sahibdir. Bu o deməkdir ki, başqa heç bir mövzu mutex ilə əhatə olunmuş kod blokundan, mutex-ə sahib olan mövzu onu açana qədər heç bir təlimatı icra edə bilməz. Mutex istifadə etmək istəyirsinizsə, proqrama mutex başlığını daxil etməlisiniz:

#include <mutex>

Bundan sonra qlobal bir dəyişən yaratmalısınız mutex növü. Paylaşılan məlumatlara girişi sinxronizasiya etmək üçün istifadə olunacaq:

Proqramın bir hissəsinin eyni dövrdə yalnız bir mövzu ilə icra olunmasını istədikdən sonra mutex istifadə edərək onu "kilidləməlisiniz":

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();
}

Elementləri vektora itələmək və yaratma əməliyyatları mutex istifadə edərək kilidlənir. Buna görə də, bir mövzu təlimat blokuna daxil olarsa və mutex-i kilidləyirsə, heç bir mövzu mutex açılmayana qədər bu kodu icra edə bilməz. Bu proqramı yenidən icra etməyə çalışın:
//create two threads
thread push(push);
thread pop(pop);
if (push.joinable())
	push.join();
if (pop.joinable())
	pop.join();

İndi vektora giriş sinxronizasiya olunur:

0 düyməsini basın
1 düyməsini basın
2 düyməsini basın
3 düyməsini basın
4 düyməsini basın
5 düyməsini basın
6 düyməsini basın
7 düyməsini basın
8 düyməsini basın
9 düyməsini basın
Pop 9
Pop 8
Pop 7
Pop 6
Pop 5
Pop 4
Pop 3
Pop 2
Pop 1
Pop 0

Mutex istifadəsinin başqa bir nümunəsini nəzərdən keçirə bilərik. Aşağıdakı vəziyyəti təsəvvür edin:

“Bir çox insan dostu ilə danışmaq üçün zəng qutusuna qaçır. Çağırış qutusunun qapı sapını tutan ilk şəxs telefondan istifadə etməyə icazə verilən yeganə şəxsdir. Zəng qutusundan istifadə etdiyi müddətdə qapının sapından tutmağa davam etməlidir. Əks təqdirdə başqası sapı tutacaq, çölə atacaq və dostu ilə danışacaq. Real həyatda olduğu kimi növbə sistemi yoxdur. Şəxs zəngini bitirdikdə, zəng qutusundan çıxıb qapının qulpundan çıxdıqda, qapı qulpundan yapışan növbəti şəxsə telefondan istifadə etməyə icazə veriləcək. ”

Bu vəziyyətdə, məlumatlara eyni vaxtda giriş problemini aşağıdakı şəkildə təsəvvür etməlisiniz:

A sap bir şəxsdir.
The mutex qapı sapıdır.
The bağlamaq şəxsin əlidir.
The resurs telefon.

Eyni zamanda digər iplər tərəfindən yerinə yetirilməməli olan bəzi kod sətirlərini yerinə yetirməli olan hər hansı bir mövzu (dostu ilə danışmaq üçün telefondan istifadə edərək) əvvəlcə bir muteksdə bir kilid əldə etməlidir (zəngin qapı qolundan tutaraq). -Qutu). Yalnız bundan sonra bir mövzu bu kod satırlarını işlədə biləcək (telefon danışığı).

Mövzu bu kodu icra etməyi bitirdikdən sonra, mutexdəki kilidi sərbəst buraxmalıdır ki, başqa bir mövzu mutexdə bir kilid əldə edə bilsin (digər insanlar telefon kabinəsinə daxil ola bilərlər).

Mutex istifadə edərək yazılmış bu vəziyyətin bir nümunəsidir:

std::mutex m;//door handle

void makeACall()
{
	m.lock();//person enters the call box and locks the door
	//now it can talk to his friend without any interruption
	cout << " Hello my friend, this is " << this_thread::get_id() << endl;
	//this person finished to talk to his friend
	m.unlock();//and he leaves the call box and unlock the door
}
int main()
{
	//create 3 persons who want to make a call from call box
	thread person1(makeACall);
	thread person2(makeACall);
	thread person3(makeACall);
	if (person1.joinable())
	{
		person1.join();
	}
	if (person2.joinable())
	{
		person2.join();
	}
	if (person3.joinable())
	{
		person3.join();
	}
}

MakeACall funksiyasına giriş senkronize ediləcəkdir. Buna bənzər bir nəticə əldə edəcəksiniz:

Salam dostum bu 3636
Salam dostum bu 5680
Salam dostum bu 928

Crack Sistemi Dizayn Müsahibələri
Translate »
1