Memahami Parallelisme dan Konkurensi dalam Pengembangan Aplikasi

Ahmad Firdaus
Developer Story
Published in
4 min readJan 18, 2024

Dalam dunia pengembangan perangkat lunak, istilah parallel dan concurrent sering kali digunakan. Tidak jarang keduanya dianggap sama, padahal keduanya sangatlah berbeda. Pemahaman yang benar terhadap kedua istilah ini sangat penting agar kita dapat mengoptimalkan kinerja aplikasi.

Parallelisme

Parallelisme adalah konsep di mana beberapa proses dijalankan secara bersamaan — benar-benar bersamaan — . Sebagai contoh, proses A dijalankan di komputer X dan proses B di komputer Y. Keduanya berjalan di waktu yang sama, pada infrastruktur yang berbeda. Parallel memungkinkan tugas-tugas berat dijalankan dengan lebih cepat, meski membutuhkan infrastruktur yang besar atau banyak, misalnya sistem dengan multi-processor atau multi-server.

Praktek yang sering dilakukan salah satunya adalah dengan publish message ke streaming system seperti Kafka, lalu proses-proses yang ada di beberapa server berbeda melakukan consume message dan menjalankannya di masing-masing server.

Konkurensi

Berbeda dengan parallelisme, konkurensi (concurrency) adalah konsep dimana beberapa proses dipecah menjadi beberapa bagian dan dijalankan secara bergantian. Misalnya, ada dua proses, yaitu A dan B; yang masing-masing terbagi menjadi tiga bagian. Proses A terdiri atas A1, A2, dan A3; sedangkan proses B terdiri atas B1, B2, dan B3. Sistem akan menjalankan bagian-bagian tersebut secara bergantian, misalnya A1, B1, A2, B2, A3, B3. Meskipun tampak seakan-akan kedua proses berjalan bersamaan, pada kenyataannya sistem hanya menjalankan satu bagian proses dalam satu waktu. Pengendalian konkurensi (concurrency control) dapat dilakukan oleh aplikasi atau oleh sistem operasi.

Kendali oleh Sistem Operasi

Jika sistem operasi mengendalikan konkurensi, maka setiap prosesnya dikenal sebagai thread. Pendekatan ini memudahkan pengembangan aplikasi karena perpindahan antar thread ditentukan secara otomatis oleh sistem operasi. Kelemahannya, proses cenderung lebih lambat karena sistem operasi tidak tahu kapan thread sedang sibuk atau idle. Misalnya, sebuah thread mendapatkan waktu operasi 100 ms, tetapi sesungguhnya thread tersebut hanya memerlukan 10 ms untuk operasi komputasi, lalu menunggu respon selama 500 ms. Hal ini menyebabkan sisa waktu (90 ms) terbuang percuma, karena tidak ada operasi yang dilakukan (idle). Jika kita mengetahui mana thread yang lebih critical, kita bisa menentukan prioritas berbeda antara thread, sehingga thread yang critical mendapat jatah lebih lama atau lebih sering. Berikut adalah contohnya:

using System;
using System.Threading;

class Program
{
static void Main()
{
// Membuat thread baru untuk proses pertama
Thread thread1 = new Thread(new ThreadStart(Proses1));
// Membuat thread baru untuk proses kedua
Thread thread2 = new Thread(new ThreadStart(Proses2));

// Memulai eksekusi kedua thread
thread1.Start();
thread2.Start();

// Menunggu kedua thread selesai
thread1.Join();
thread2.Join();

Console.WriteLine("Kedua proses telah selesai.");
}

static void Proses1()
{
// Simulasi tugas dalam proses 1
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Proses 1 di iterasi {i}");
Thread.Sleep(500); // Tidur selama 500 milidetik
}
}

static void Proses2()
{
// Simulasi tugas dalam proses 2
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Proses 2 di iterasi {i}");
Thread.Sleep(500); // Tidur selama 500 milidetik
}
}
}

Kendali oleh Aplikasi

Dalam pendekatan ini, setiap proses disebut sebagai task. Task adalah proses yang hanya menggunakan satu thread saja (single thread). Task akan terus beroperasi hingga menemukan perintah “await”, yang menandakan proses sedang menunggu respon dari I/O, misalnya menunggu hasil keluaran query ke Database. Begitu menemukan perintah await, maka jatah waktu diberikan ke task lain yang sedang aktif melakukan komputasi. Dengan demikian, pemanfaatan jatah menjadi jauh lebih optimal. Salah satu kesalahan yang sering dilakukan oleh programmer adalah tidak melakukan await saat melakukan pemanggilan ke I/O. Akibatnya, seluruh task, yaitu task yang sedang aktif dan task lain yang sedang menunggu giliran akan ikut berhenti karena menunggu hasil pengembalian dari I/O. Kejadian ini disebut blocking, yang dampaknya membuat kinerja aplikasi menurun drastis. Berikut adalah contohnya:

using System;
using System.Threading.Tasks;

class Program
{
static async Task Main(string[] args)
{
// Memulai kedua tugas secara asinkron
Task tugas1 = TugasAsinkron1();
Task tugas2 = TugasAsinkron2();

// Menunggu kedua tugas selesai
await Task.WhenAll(tugas1, tugas2);

Console.WriteLine("Kedua tugas telah selesai.");
}

static async Task TugasAsinkron1()
{
Console.WriteLine("Tugas 1 dimulai.");
await Task.Delay(2000); // Menyimulasikan tugas yang membutuhkan waktu
Console.WriteLine("Tugas 1 selesai.");
}

static async Task TugasAsinkron2()
{
Console.WriteLine("Tugas 2 dimulai.");
await Task.Delay(1000); // Menyimulasikan tugas yang membutuhkan waktu
Console.WriteLine("Tugas 2 selesai.");
}
}

Pemilihan Pendekatan

Pemilihan antara kontrol manual atau kontrol oleh sistem operasi tergantung pada kebutuhan aplikasi. Pada kenyataannya, akses ke I/O bisa memakan waktu 10.000x (sepuluh ribu kali) lebih lambat ketimbang komputasi atau akses ke memory. Jika developer ingin menjalankan beberapa proses dalam satu aplikasi, maka pilihan manual kontrol hampir selalu merupakan pilihan yang lebih efisien dibanding kontrol oleh OS.

Namun, dalam kondisi tertentu, seperti saat kita harus menjalankan dua proses yang benar-benar independen dengan kebutuhan waktu tertentu, maka multi-threading yang dikontrol oleh sistem operasi adalah pilihan yang lebih tepat.

Memahami perbedaan dan penerapan antara parallel dan concurrent programming tidak hanya penting dalam teori, tetapi juga sangat krusial dalam praktik pengembangan aplikasi. Developer yang memahami kedua konsep ini dapat membuat keputusan arsitektur yang lebih tepat, sehingga meningkatkan efisiensi dan kinerja aplikasi secara signifikan.

Kesimpulan

Kemampuan untuk membedakan dan menerapkan parallel dan concurrent programming merupakan keahlian penting dalam pengembangan aplikasi. Pemahaman yang benar akan membantu menghindari bottleneck kinerja dan memanfaatkan sumber daya komputasi dengan lebih efektif. Parallelisme dan konkurensi, meskipun sering dianggap mirip, memiliki perbedaan mendasar yang jika diterapkan dengan tepat dapat menghasilkan peningkatan performa yang signifikan pada aplikasi. Developer harus mempertimbangkan kebutuhan spesifik aplikasi mereka dan memilih pendekatan yang paling sesuai, apakah itu parallel, concurrent, atau kombinasi keduanya.

Hujan deras melanda Jakarta,
Macet dan banjir dimana-mana.
Meski Parallel dan concurrent tampak sama,
Tapi sungguh keduanya amatlah berbeda

--

--