GNU/Linux >> Belajar Linux >  >> Linux

Cara Menggunakan Contoh Kunci C Mutex untuk Sinkronisasi Utas Linux

Dalam seri utas Linux, kami membahas tentang cara utas dapat dihentikan dan bagaimana status pengembalian diteruskan dari utas pengakhiran ke utas induknya. Dalam artikel ini kami akan menyoroti aspek penting yang dikenal sebagai sinkronisasi utas.

Linux Threads Series:bagian 1, bagian 2, bagian 3, bagian 4 (artikel ini).

Masalah Sinkronisasi Thread

Mari kita ambil contoh kode untuk mempelajari masalah sinkronisasi :

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid[2];
int counter;

void* doSomeThing(void *arg)
{
    unsigned long i = 0;
    counter += 1;
    printf("\n Job %d started\n", counter);

    for(i=0; i<(0xFFFFFFFF);i++);
    printf("\n Job %d finished\n", counter);

    return NULL;
}

int main(void)
{
    int i = 0;
    int err;

    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        i++;
    }

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);

    return 0;
}

Kode di atas adalah kode sederhana di mana dua utas (pekerjaan) dibuat dan dalam fungsi awal utas ini, penghitung dipertahankan di mana pengguna mendapatkan log tentang nomor pekerjaan yang dimulai dan kapan selesai. Kode dan alurnya terlihat bagus tetapi ketika kita melihat outputnya :

$ ./tgsthreads
Job 1 started
Job 2 started
Job 2 finished
Job 2 finished

Jika Anda fokus pada dua log terakhir, Anda akan melihat bahwa log 'Pekerjaan 2 selesai' diulang dua kali sementara tidak ada log untuk 'Pekerjaan 1 selesai' yang terlihat.

Sekarang, jika Anda kembali ke kode dan mencoba menemukan kesalahan logis, Anda mungkin tidak akan menemukan kesalahan dengan mudah. Namun jika Anda melihat lebih dekat dan memvisualisasikan eksekusi kode, Anda akan menemukan bahwa :

  • Log 'Pekerjaan 2 dimulai' dicetak tepat setelah 'Pekerjaan 1 Dimulai' sehingga dapat dengan mudah disimpulkan bahwa saat utas 1 sedang memproses penjadwal menjadwalkan utas 2.
  • Jika asumsi di atas benar maka nilai variabel 'counter' bertambah lagi sebelum job 1 selesai.
  • Jadi, ketika Pekerjaan 1 benar-benar selesai, maka nilai penghitung yang salah menghasilkan log 'Pekerjaan 2 selesai' diikuti oleh 'Pekerjaan 2 selesai'  untuk pekerjaan 2 yang sebenarnya atau sebaliknya karena tergantung pada scheduler.
  • Jadi kami melihat bahwa bukan log berulang tetapi nilai yang salah dari variabel 'penghitung' yang menjadi masalah.

Masalah sebenarnya adalah penggunaan variabel 'penghitung' oleh utas kedua ketika utas pertama menggunakan atau akan menggunakannya. Dengan kata lain kita dapat mengatakan bahwa kurangnya sinkronisasi antara utas saat menggunakan 'penghitung' sumber daya bersama menyebabkan masalah atau dengan kata lain kita dapat mengatakan bahwa masalah ini terjadi karena 'masalah sinkronisasi' antara dua utas.

Mutex

Sekarang karena kita telah memahami masalah dasarnya, mari kita bahas solusinya. Cara paling populer untuk mencapai sinkronisasi utas adalah dengan menggunakan Mutex.

Mutex adalah kunci yang kami atur sebelum menggunakan sumber daya bersama dan lepaskan setelah menggunakannya. Saat kunci disetel, tidak ada utas lain yang dapat mengakses wilayah kode yang dikunci. Jadi kita melihat bahwa bahkan jika utas 2 dijadwalkan sementara utas 1 tidak selesai mengakses sumber daya bersama dan kode dikunci oleh utas 1 menggunakan mutex, maka utas 2 bahkan tidak dapat mengakses wilayah kode itu. Jadi ini memastikan akses yang disinkronkan dari sumber daya bersama dalam kode.

Secara internal berfungsi sebagai berikut :

  • Misalkan satu utas telah mengunci wilayah kode menggunakan mutex dan mengeksekusi potongan kode tersebut.
  • Sekarang jika penjadwal memutuskan untuk melakukan pengalihan konteks, maka semua utas lain yang siap untuk mengeksekusi wilayah yang sama tidak diblokir.
  • Hanya satu dari semua utas yang akan berhasil dieksekusi, tetapi jika utas ini mencoba mengeksekusi wilayah kode yang sama yang sudah dikunci, maka utas akan kembali ke mode tidur.
  • Perubahan konteks akan terjadi berulang kali tetapi tidak ada utas yang dapat mengeksekusi wilayah kode yang dikunci sampai kunci mutex di atasnya dilepaskan.
  • Kunci mutex hanya akan dilepaskan oleh utas yang menguncinya.
  • Jadi ini memastikan bahwa setelah sebuah utas mengunci sepotong kode, maka tidak ada utas lain yang dapat mengeksekusi wilayah yang sama sampai utas itu dibuka oleh utas yang menguncinya.
  • Oleh karena itu, sistem ini memastikan sinkronisasi antar utas saat mengerjakan sumber daya bersama.

Sebuah mutex diinisialisasi dan kemudian kunci dicapai dengan memanggil dua fungsi berikut:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);

Fungsi pertama menginisialisasi mutex dan melalui fungsi kedua setiap wilayah kritis dalam kode dapat dikunci.

Mutex dapat dibuka dan dihancurkan dengan memanggil fungsi berikut:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Fungsi pertama di atas melepaskan kunci dan fungsi kedua menghancurkan kunci sehingga tidak dapat digunakan di mana pun di masa mendatang.

Contoh Praktis

Mari kita lihat sepotong kode di mana mutex digunakan untuk sinkronisasi utas

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid[2];
int counter;
pthread_mutex_t lock;

void* doSomeThing(void *arg)
{
    pthread_mutex_lock(&lock);

    unsigned long i = 0;
    counter += 1;
    printf("\n Job %d started\n", counter);

    for(i=0; i<(0xFFFFFFFF);i++);

    printf("\n Job %d finished\n", counter);

    pthread_mutex_unlock(&lock);

    return NULL;
}

int main(void)
{
    int i = 0;
    int err;

    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("\n mutex init failed\n");
        return 1;
    }

    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        i++;
    }

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    pthread_mutex_destroy(&lock);

    return 0;
}

Pada kode di atas :

  • Mutex diinisialisasi di awal fungsi utama.
  • Mutex yang sama dikunci dalam fungsi 'doSomeThing()' saat menggunakan 'penghitung' sumber daya bersama
  • Di akhir fungsi 'doSomeThing()' mutex yang sama akan dibuka.
  • Di akhir fungsi utama ketika kedua utas selesai, mutex dihancurkan.

Sekarang jika kita melihat output, kita menemukan :

$ ./threads
Job 1 started
Job 1 finished
Job 2 started
Job 2 finished

Jadi kita melihat bahwa kali ini log awal dan akhir dari kedua pekerjaan hadir. Jadi sinkronisasi utas dilakukan dengan menggunakan Mutex.


Linux
  1. Cara menggunakan rsync lanjutan untuk cadangan Linux besar

  2. Cara menggunakan systemd-nspawn untuk pemulihan sistem Linux

  3. Cara Menggunakan Perintah Linux Rsync (Sinkronisasi Jarak Jauh)

  1. Cara Menggunakan Perintah Sentuh Linux + Contoh

  2. Cara Menggunakan Perintah Hexdump Linux dengan Contoh Praktis

  3. Perintah SCP di Linux:Cara Menggunakannya, dengan Contoh

  1. Bagaimana saya menggunakan Cockpit untuk manajemen server Linux di rumah saya

  2. Cara Menggunakan Linux di Command

  3. Cara Menggunakan Perintah IP di Linux dengan Contoh