Skip to content

Commit 2fe8a76

Browse files
committed
Updated the chapter about threads.
1 parent d186c19 commit 2fe8a76

File tree

4 files changed

+237
-27
lines changed

4 files changed

+237
-27
lines changed

Lesson_11/README.md

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,223 @@ nesneleri de bir vektörde toplanır ve son aşamada tamamının sonuçları ür
167167

168168
## Atomic Reference Counting
169169

170-
// todo@buraksenyurt Not Implemented Yet
170+
Arc türününün kullanımını anlamak için öncelikle sorunu ortaya koymamız gerekir. Aşağıdaki örnek kod parçasını göz önüne
171+
alalım.
172+
173+
```rust
174+
use std::thread;
175+
176+
fn main() {
177+
run_with_error();
178+
println!("After the thread calling");
179+
}
180+
181+
pub fn run_with_error() {
182+
let data = vec![
183+
"Service Red: Task A",
184+
"Service Blue: Task B",
185+
"Service Green: Task C",
186+
"Service Alpha: Task D",
187+
];
188+
189+
let mut handles = vec![];
190+
for i in 0..2 {
191+
let handle = thread::spawn(move || {
192+
for task in &data {
193+
println!("Thread '{}' is processing '{}'", i, task);
194+
}
195+
});
196+
197+
handles.push(handle);
198+
}
199+
200+
for handle in handles {
201+
handle.join().unwrap();
202+
}
203+
}
204+
```
205+
206+
Bu kod derlenmeyecek ve aşağıdaki hata mesajı üretilecektir.
207+
208+
```text
209+
let data = vec![
210+
---- move occurs because `data` has type `Vec<&str>`, which does not implement the `Copy` trait
211+
212+
for i in 0..2 {
213+
------------- inside of this loop
214+
let handle = thread::spawn(move || {
215+
^^^^^^^ value moved into closure here, in previous iteration of loop
216+
for task in &data {
217+
---- use occurs due to use in closure
218+
```
219+
220+
Senaryoda data isimli vektörü kullanmak isteyen 2 farklı thread oluşturulmak istenmektedir. Sorun aynı anda birden fazla
221+
thread'in ortak bir referansa erişmeye çalışmasıdır zira data değişkeninin içeriği closure ifadesi içerisinde
222+
alındığında sahiplikte bu bloğa geçer. Dolayısıyla Rust'ın sahiplik ilkeleri gereği thread safe bir ortamın sağlanması
223+
için bu referans veri başka bir thread tarafından kullanılamaz. Çözüm olarak Arc türünden yararlanılabilir. Aşağıdaki
224+
kod örneğinde bu kullanıma yer verilmektedir.
225+
226+
```rust
227+
use std::sync::Arc;
228+
use std::thread;
229+
230+
fn main() {
231+
run_correctly();
232+
println!("After the thread calling");
233+
}
234+
235+
pub fn run_correctly() {
236+
let data = Arc::new(vec![
237+
"Service Red: Task A",
238+
"Service Blue: Task B",
239+
"Service Green: Task C",
240+
"Service Alpha: Task D",
241+
]);
242+
243+
let mut handles = vec![];
244+
245+
for i in 0..2 {
246+
let data_clone = Arc::clone(&data);
247+
let handle = thread::spawn(move || {
248+
for task in data_clone.iter() {
249+
println!("Thread '{}' is processing '{}'", i, task);
250+
}
251+
});
252+
253+
handles.push(handle);
254+
}
255+
256+
for handle in handles {
257+
handle.join().unwrap();
258+
}
259+
}
260+
```
261+
262+
Bir önceki örnekten farklı olarak data isimli değişken oluşturulurken vektörün kendisi yeni bir atomik referans sayacına
263+
devredilir. İkinci önemli nokta thread açılmadan önce herbir thread bloğuna bu Arc referansının bir klonunun
264+
atanmasıdır. clone metodu çağırılırken data değişkeninin referansının verildiğine de dikkat edilmelidir. Böylece data
265+
değişkenin işaret ettiği veriye her thread güvenli bir şekilde erişir, Arc söz konusu referans klonlarını sayar ve drop
266+
sırasında da bunları sondan başa doğru kaldırır. Ne var ki thread'lerin veriye güvenli erişimi söz konusu olsa da bazı
267+
senaryolarda verinin tutarlılığının bozulma ihtimali vardır.
268+
269+
Şimdiki senaryoda thread'lerin aynı veri üzerinde değişiklik yapmak istediğiniz düşünelim. Bu amaçla koduuzu biraz
270+
değiştirip aşağıdaki hale getirelim.
271+
272+
```rust
273+
use std::sync::Arc;
274+
use std::thread;
275+
276+
fn main() {
277+
run_inconsistent();
278+
println!("After the thread calling");
279+
}
280+
281+
pub fn run_inconsistent() {
282+
let data = Arc::new(vec![0; 10]);
283+
let mut handles = vec![];
284+
285+
for i in 0..4 {
286+
let data_clone = Arc::clone(&data);
287+
let handle = thread::spawn(move || {
288+
for j in 0..data_clone.len() {
289+
data_clone[j] += 2;
290+
println!("Thread {} updating index {}", i, j);
291+
}
292+
});
293+
294+
handles.push(handle);
295+
}
296+
297+
for handle in handles {
298+
handle.join().unwrap();
299+
}
300+
301+
println!("{:?}", *data);
302+
}
303+
```
304+
305+
Bu kod parçası da derlenmeyecek ve aşağıdaki hatayı üretecektir.
306+
307+
```text
308+
error[E0596]: cannot borrow data in an `Arc` as mutable
309+
--> Lesson_11\src\main.rs:17:17
310+
|
311+
17 | data_clone[j] += 2;
312+
| ^^^^^^^^^^ cannot borrow as mutable
313+
|
314+
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<Vec<i32>>`
315+
```
316+
317+
Arc referansının klonu alınarak üzerinde değişiklik yapılmak istenmektedir ancak Arc veriye eş zamanlı olarak güvenli
318+
erişim sağlarken onun mutable olmasına izin vermez. Bir başka deyişle veriye eş zamanlı erişim Arc kullanımı ile
319+
mümkünken güvenli bir şekilde değiştirilmesi için farklı bir metodoloji kullanmamız gerekir. Buna istinaden aşağıdaki
320+
örneği göz önüne alabiliriz.
321+
322+
```rust
323+
use std::sync::{Arc, Mutex};
324+
use std::thread;
325+
use std::time::Duration;
326+
327+
fn main() {
328+
run_mutex();
329+
println!("After the thread calling");
330+
}
331+
332+
pub fn run_mutex() {
333+
let data = Arc::new(Mutex::new(0));
334+
335+
let data_clone_one = Arc::clone(&data);
336+
let t1 = thread::spawn(move || {
337+
let mut num = data_clone_one.lock().unwrap();
338+
*num += 3;
339+
println!("Thread 1 has locked the data.");
340+
thread::sleep(Duration::from_secs(3)); // Kasıtlı olarak bekletme yapıyoruz
341+
println!("After 3 seconds...\nThread 1 is unlocking the data.");
342+
});
343+
344+
let data_clone_two = Arc::clone(&data);
345+
let t2 = thread::spawn(move || {
346+
println!("Thread 2 is trying to lock the data.");
347+
let mut num = data_clone_two.lock().unwrap();
348+
*num += 5;
349+
println!("Thread 2 has locked and updated the data.");
350+
});
351+
352+
t1.join().unwrap();
353+
t2.join().unwrap();
354+
355+
println!("Final value: {}", *data.lock().unwrap());
356+
}
357+
```
358+
359+
Değiştirilmek istenen veri öncelikle bir **Mutex** nesnesinin koruması altına bırakılır ve **Arc** smart pointer'ı ile
360+
ilişkilendirilir. Buna göre farklı thread'ler söz konusu veriye güvenli erişebilir ama değiştirmek istediklerinde bir
361+
kilit mekanizması koyarak diğer thread'lerin aynı veriyi değiştirmesini engeller. **Thread**'ler bir **closure** ifadesi
362+
ile başlatıldığında kilit mekanizması bu blok içerisinde konur ve scope dışına gelindiğinde söz konusu kilit otomatik
363+
olarak düşer. Bu nedenle yukarıdaki örnek kod parçası sorunsuz ve **thread-safe** bir şekilde çalışır. Örnek kod
364+
parçasında gerçekten kilit mekanizmalarının çalıştığını gözlemlemek için bazı bildirimler eklenmiş ve hayali bekleme
365+
süresi konulmuştur. Buna göre beklenen çalışma zamanı çıktısı aşağıdaki gibidir.
366+
367+
![mutex runtime.png](mutexRuntime.png)
171368

172369
## Thread Poisoning
173370

174371
// todo@buraksenyurt Not Implemented Yet
175372

176373
## Concurrency vs Parallel Programming
177374

178-
// todo@buraksenyurt Not Implemented Yet
375+
Eş zamanlılık _(Concurrency)_ ve paralel programlama sıksık birbirlerine karıştırılırlar. **Concurrency** genel olarak
376+
birden fazla işin aynı anda başlatılmas ve yönetilmesi olarak tanımlanır. Fakat birden fazla işin fiziksel olarak aynı
377+
anda işletilmesi paralel programlama olarak tanımlanır. **Concurrency**, başlatılan görevlerin birbirine karışmadan
378+
yönetilmesine odaklanırken paralel programlamada işlerin gerçekten fiziksel kaynaklar arasında bölüşülerek
379+
hızlandırılması esastır. Dolayısıyla paralel programlama da donanım yani çekirdek sayıları öne çıkar. Görüldüğü üzere
380+
tanımlar birbirine çok yakındır bu nedenle karıştırılmaları da doğaldır. **Concurrency** de işler sırasıyla veya
381+
birbirine bağımlı olmadan işletilirken genellikle yardımcı bazı küfeler kullanılır. **async/await** kullanımları, *
382+
*Future** ve **tokio** gibi kütüphaneler concurrent programlama için kullanılır. Paralel programlama çoğunlukla işletim
383+
sistemi seviyesinde ve thread enstrümanı kullanılarak icra edilir. Rust tarafında spawn metodu basitçe thread başlatmak
384+
için yeterlidir ancak işlemci çekirdeklerinden daha iyi yararlanabilmek için **rayon** gibi harici küfeler _(crates)_
385+
kullanılır. Asenkron programlama verilebilecek en güzel örneklerden birisi web sunucusudur. Web sunucuları eş zamanlı
386+
olarak gelen sayısız isteği karşılamakla yükümlüdür. Buradaki işlevsellik paralel programladan ziyade gelen taleplerin
387+
asenkron olarak belli bir planlayıcı dahilinde birbirlerini beklemden yürütülebilmesidir. Bir başka eş zamanlı çalışma
388+
örneği de asenkron icra edilen **I/O** işlemleridir. Paralel programlama ileri seviye hesaplamalar gereken süreçlerde
389+
veya büyük verilerin işlenmesi hatta geniş dil modellerinde çok daha etkilidir.

Lesson_11/mutexRuntime.png

21.6 KB
Loading

Lesson_11/src/main.rs

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
1+
use std::sync::{Arc, Mutex};
12
use std::thread;
3+
use std::time::Duration;
24

35
fn main() {
4-
multiple_threads_sample();
6+
run_mutex();
57
println!("After the thread calling");
68
}
79

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;
10+
pub fn run_mutex() {
11+
let data = Arc::new(Mutex::new(0));
1612

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-
}
13+
let data_clone_one = Arc::clone(&data);
14+
let t1 = thread::spawn(move || {
15+
let mut num = data_clone_one.lock().unwrap();
16+
*num += 3;
17+
println!("Thread 1 has locked the data.");
18+
thread::sleep(Duration::from_secs(3)); // Kasıtlı olarak bekletme yapıyoruz
19+
println!("After 3 seconds...\nThread 1 is unlocking the data.");
20+
});
2821

29-
let mut final_results = vec![];
22+
let data_clone_two = Arc::clone(&data);
23+
let t2 = thread::spawn(move || {
24+
println!("Thread 2 is trying to lock the data.");
25+
let mut num = data_clone_two.lock().unwrap();
26+
*num += 5;
27+
println!("Thread 2 has locked and updated the data.");
28+
});
3029

31-
for handle in handles {
32-
final_results.push(handle.join().unwrap());
33-
}
30+
t1.join().unwrap();
31+
t2.join().unwrap();
3432

35-
println!("{:?}", final_results);
33+
println!("Final value: {}", *data.lock().unwrap());
3634
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ yer aldığı repodur.
2424
- [Birim Test](UnitTests.md)
2525
- [Hata Yönetimi](ErrorHandling.md)
2626
- [Temel I/O İşlemleri](./io.md)
27+
- [Streams]()
2728
- [Yardımcı Kaynaklar](#yardımcı-kaynaklar)
2829
- [Örnek Uygulamalar](#örnek-uygulamalar)
2930

0 commit comments

Comments
 (0)