Skip to content

Commit d186c19

Browse files
committed
Opened a new chapter about threads.
1 parent 1da97bf commit d186c19

File tree

6 files changed

+222
-2
lines changed

6 files changed

+222
-2
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[workspace]
2-
members = ["Lesson_00", "Lesson_01", "Lesson_02", "Lesson_03", "Lesson_04", "Lesson_05", "Lesson_06", "Lesson_07", "Lesson_08", "Lesson_09", "Lesson_10", "collector", "drone-lab", "fops", "mocking", "sysco", "sysco2"]
2+
members = ["Lesson_00", "Lesson_01", "Lesson_02", "Lesson_03", "Lesson_04", "Lesson_05", "Lesson_06", "Lesson_07", "Lesson_08", "Lesson_09", "Lesson_10", "Lesson_11", "collector", "drone-lab", "fops", "mocking", "sysco", "sysco2"]
33

44
resolver = "2"

Lesson_11/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "Lesson_11"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]

Lesson_11/README.md

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Ders 11: Multi-Thread Programlama
2+
3+
İşletim sistemi tarafında bir uygulama başlatıldığında tekil bir process içerisinde bir ana iş parçacığı _(main thread)_
4+
açılır. Bazı durumlarda program tarafından birden fazla işin eş zamanlı olarak işletilmesi gerekir. Bu durumda
5+
genellikle yeni thread'ler oluşturulur. Birden fazla iş parçacığını aynı anda çalıştırmak performansı artırmak için
6+
idealdir ancak çalışması karmaşıktır. Problemlerden birisi farklı thread'lerin aynı veriye erişip kullanmaya
7+
çalışmasıdır. Thread'ler senkronize edilmediklerinde veriye tutarsız sıralarda erişebilirler. Bu genellikle **Race
8+
Condition** olarak adlandırılan problemin oluşmasına sebebiyet verir. Bir diğer sorun iki iş parçacığının birbirinin
9+
beklemesi durumudur ve Deadlock olarak adlandırılır. Her iki problem de eş zamanlı programlamanın _(Concurrent
10+
Programming)_ thread kullanılan senaryolarda en çok karşımıza çıkanlar arasındadır.
11+
12+
![OS Threads.png](processes.png)
13+
_İşletim Sisteminde Process ve Thread Durumları_
14+
15+
## Thread Oluşturmak
16+
17+
Aşağıdaki kod parçasında ana thread dışında yeni bir thread daha oluşturulması ele alınmaktadır.
18+
19+
```rust
20+
use std::thread;
21+
use std::time::Duration;
22+
23+
fn main() {
24+
start_a_simple_thread();
25+
println!("After the thread calling");
26+
}
27+
28+
pub fn start_a_simple_thread() {
29+
let handle = thread::spawn(move || {
30+
println!("Starting thread...");
31+
thread::sleep(Duration::new(3, 0));
32+
println!("Thread stopped...");
33+
});
34+
handle.join().unwrap();
35+
}
36+
```
37+
38+
Bir thread oluşturmak için spawn metodu kullanılır. Bu metot FnOnce trait uygulayan herhangi bir kod bloğunu parametre
39+
olarak alabilir. Dışırdan thread içerisine alınacak değişkenler için move operatörü kullanılır. Ayrıca spawn metodu
40+
geriye bir JoinHandle türü döndürür. Örnekte dikkat edileceği üzere handle üzerinden join çağrısı yapılmıştır. Bu
41+
yapılmadığı takdirde program çalışmakta olan ikinci thread'in işleyişinin bitmesini beklemeden devam eder ve sonlanır.
42+
Yani main thread'in, içeride başlatılan diğer thread'lerin işlerini bitirmeden sonlanmasını engellemek için JoinHandle
43+
nesne takibini yapmak gerekir.
44+
45+
## move Operatörü
46+
47+
Spawn metodu, parametre olarak bir closure kabul eder ve bu kod bloğuna istenirse dış thread üzerinden veri taşınabilir.
48+
Burada move operatörü ile bir bildirim yapılır. move operatörü ile ilgili durumu anlamak için aşağıdaki kod örneğini göz
49+
önüne alalım.
50+
51+
```rust
52+
use std::thread;
53+
54+
fn main() {
55+
move_keyword_error();
56+
println!("After the thread calling");
57+
}
58+
59+
pub fn move_keyword_error() {
60+
let student_points = vec![30.50, 49.90, 65.55, 90.00, 81.00];
61+
let handle = thread::spawn(|| {
62+
println!("Thread is starting...");
63+
println!("{:?}", student_points);
64+
println!("Thread completed");
65+
});
66+
handle.join().unwrap();
67+
}
68+
```
69+
70+
Ana thread içinde tanımlı olan student_points vektörünün yeni başlatılan bir thread içerisinde kullanılması
71+
örnekleniyor. Eğer move operatörünü kullanmazsak derleyici şöyle bir hata mesajı verecektir.
72+
73+
```text
74+
error[E0373]: closure may outlive the current function
75+
, but it borrows `student_points`, which is owned by the current function
76+
```
77+
78+
Sorun, ana thread'in sahiplendiği bir referans türünün haricen açılmış bir thread'in işlettiği closure'a ödünç verilmeye
79+
çalışılmasıdır. Dolayısıyla move keyword'ünü kullanarak derleyici bu konuda bilgilendirmek ve yaşam süresinin
80+
ayarlanmasını sağlamak gerekir. İlginç olan bir durum ise, bu vektörün elemanlarının bir for döngüsü ile ela alınması
81+
halidir. Konuyu daha iyi anlamak için kodu aşağıdaki şekilde değiştirelim.
82+
83+
```rust
84+
use std::thread;
85+
86+
fn main() {
87+
move_keyword_error();
88+
println!("After the thread calling");
89+
}
90+
91+
pub fn move_keyword_error() {
92+
let student_points = vec![30.50, 49.90, 65.55, 90.00, 81.00];
93+
let handle = thread::spawn(|| {
94+
println!("Thread is starting...");
95+
for point in student_points {
96+
println!("Processing for point {}", point);
97+
}
98+
println!("Thread completed");
99+
});
100+
handle.join().unwrap();
101+
}
102+
```
103+
104+
Vektör türü ile ifade edilen veriler bilindiği üzere heap üzerinde konuşlandırlır. main thread içinde başlatılan ikinci
105+
thread bu vektörün sahipliğini _(ownership)_ closure ile açılan bloğa taşımak ister. Ancak ikinci thread main thread'den
106+
daha uzun süre çalışabilir ve bu güvenli bir çalışma biçimi değildir _(Memory Safe)_ Bu yüzden rust move operatörü ile
107+
durumun açıkla belirtilmesini bekler.
108+
109+
Rust **thread safe** bir ortam sağlamaya çalışır. Birden fazla thread'in aynı bellek adresine işaret
110+
etmesi data race probleminin oluşmasına yol açabilir. **move** kullanıldığında closure ifadesi, student_points
111+
vektörünün sahipliğini alır. Vektörün sahipliği artık ana thread'e değil, closure bloğuna aittir. Rust
112+
thread’ler arası sahiplik sorununu bu şekilde çözer ve data race durumunun önüne geçilir.
113+
114+
Peki vektör elemanlarını teker teker dolaştığımızda **move** kullanılmaması neden bir probleme sebebiyet vermez? Bunun
115+
nedeni closure bloğunun vektörün kendisini değil elemanlarını kullanmasıdır. Zira bu örnekte vektör elemanları boyutu
116+
belli olan primitive tiplerden **f32** türündendir ve dolayısıyla closure ifadesine kopyalanarak taşınabilirler. Bir
117+
başka deyişle closure bu kopyalanan veriler üzerinde çalışır ki orjinal vektörün sahiplenilmesi veya borç
118+
olara alınması söz konusu olmaz.
119+
120+
Kurguya daha işe yarar bir senaryo haline getirelim. Bir sayı dizisindeki elemanların faktöryel değerlerini örneğin 4
121+
farklı iş parçacığında ele almaya çalışalım. Aşağıdaki örnek kod parçasında bu durum ele alınmaktadır.
122+
123+
```rust
124+
use std::thread;
125+
126+
fn main() {
127+
multiple_threads_sample();
128+
println!("After the thread calling");
129+
}
130+
131+
fn calc_factorial(n: u64) -> u64 {
132+
(1..=n).product()
133+
}
134+
pub fn multiple_threads_sample() {
135+
let numbers = vec![10, 3, 5, 13, 8, 9, 1, 2, 17, 11, 7, 6];
136+
let threads_count = 4;
137+
let mut handles = vec![];
138+
let chunk_size = numbers.len() / threads_count;
139+
140+
for i in 0..threads_count {
141+
let chunk = numbers[i * chunk_size..(i + 1) * chunk_size].to_vec();
142+
handles.push(thread::spawn(move || {
143+
let mut results = vec![];
144+
for num in chunk {
145+
println!("Thread {} processing for {}", i, num);
146+
results.push((num, calc_factorial(num)));
147+
}
148+
results
149+
}));
150+
}
151+
152+
let mut final_results = vec![];
153+
154+
for handle in handles {
155+
final_results.push(handle.join().unwrap());
156+
}
157+
158+
println!("{:?}", final_results);
159+
}
160+
```
161+
162+
Numbers isimli vektörün 12 elemanı bulunmaktadır. **threads_count** değişkeni ile belirtilen sayı kadar **thread**
163+
açılır ki örneğimize göre ana thread haricinde 4 farklı thred açılması söz konusudur. Sayı dizisindeki elemanları dört
164+
parçaya bölünür ve her blok açılan thread'lerden birisine işlenmek üzere gönderilir. Birden fazla thread söz konusu
165+
olduğundan her birinin işini bitirmesinin beklenmesi gerekir. Bu yüzden **spawn** çağrılarındaki **JoinHandle**
166+
nesneleri de bir vektörde toplanır ve son aşamada tamamının sonuçları üretilene kadar beklenir.
167+
168+
## Atomic Reference Counting
169+
170+
// todo@buraksenyurt Not Implemented Yet
171+
172+
## Thread Poisoning
173+
174+
// todo@buraksenyurt Not Implemented Yet
175+
176+
## Concurrency vs Parallel Programming
177+
178+
// todo@buraksenyurt Not Implemented Yet

Lesson_11/processes.png

17.8 KB
Loading

Lesson_11/src/main.rs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::thread;
2+
3+
fn main() {
4+
multiple_threads_sample();
5+
println!("After the thread calling");
6+
}
7+
8+
fn calc_factorial(n: u64) -> u64 {
9+
(1..=n).product()
10+
}
11+
pub fn multiple_threads_sample() {
12+
let numbers = vec![10, 3, 5, 13, 8, 9, 1, 2, 17, 11, 7, 6];
13+
let threads_count = 4;
14+
let mut handles = vec![];
15+
let chunk_size = numbers.len() / threads_count;
16+
17+
for i in 0..threads_count {
18+
let chunk = numbers[i * chunk_size..(i + 1) * chunk_size].to_vec();
19+
handles.push(thread::spawn(move || {
20+
let mut results = vec![];
21+
for num in chunk {
22+
println!("Thread {} processing for {}", i, num);
23+
results.push((num, calc_factorial(num)));
24+
}
25+
results
26+
}));
27+
}
28+
29+
let mut final_results = vec![];
30+
31+
for handle in handles {
32+
final_results.push(handle.join().unwrap());
33+
}
34+
35+
println!("{:?}", final_results);
36+
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ yer aldığı repodur.
1717
- [Ders 08 - Traits](./Lesson_08/README.md)
1818
- [Ders 09 - Closures](./Lesson_09/README.md)
1919
- [Ders 10 - Smart Pointers](./Lesson_10/Readme.md)
20-
- [Ders 11 - Threads]()
20+
- [Ders 11 - Threads](./Lesson_11/README.md)
2121
- [Ders 12 - Channels]()
2222
- [Ders 13 - Macros]()
2323
- [Destekleyici Bölümler]()

0 commit comments

Comments
 (0)