Berbagi teknologi

Linux multi-proses dan multi-threading (8) Multi-threading

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Multithread

Definisi benang

Thread adalah unit eksekusi dalam suatu proses.

Bertanggung jawab atas pelaksanaan program dalam proses saat ini,

Setidaknya ada satu thread dalam suatu proses

Mungkin ada banyak thread dalam satu proses

Beberapa thread berbagi semua sumber daya dari proses yang sama, dan setiap thread berpartisipasi dalam penjadwalan terpadu sistem operasi

Secara sederhana dapat dipahami sebagai proses = sumber daya memori + thread utama + thread anak +…

Utas dan proses

Untuk tugas-tugas yang terkait erat, multi-threading lebih disukai selama konkurensi. Untuk tugas-tugas yang tidak terkait erat dan relatif independen, disarankan untuk memilih multi-proses;

  • Proses: Unit dasar alokasi sumber daya oleh sistem operasi. Ini adalah unit terkecil dari alokasi sumber daya, unit eksekusi dan penjadwalan program, dan instance program yang sedang berjalan.
  • Thread: Ini adalah unit dasar penjadwalan dan pengiriman CPU, unit terkecil eksekusi CPU, unit terkecil aliran eksekusi program, dan unit terkecil eksekusi program.

Perbedaan antara thread dan proses:

  • ruang memori
    • Beberapa thread dalam suatu proses berbagi ruang memori yang sama
    • Beberapa proses memiliki ruang memori independen
  • Komunikasi antar proses/utas
    • Komunikasi sederhana antar thread
    • Komunikasi antar-proses itu rumit

Sumber daya benang

  • Sumber daya proses bersama
    • ruang alamat yang sama
    • tabel deskriptor file
    • Bagaimana setiap sinyal diproses
    • direktori kerja saat ini
    • id pengguna dan id grup
  • Sumber daya yang unik
    • Tumpukan benang
    • Setiap thread memiliki informasi konteks pribadi
    • ID benang
    • Daftarkan nilai
    • nilai yang salah
    • Sinyal menutupi kata-kata dan prioritas penjadwalan

Perintah terkait thread

Ada banyak perintah untuk melihat proses di sistem Linux, antara lain pidstat, top, ps, Anda dapat melihat prosesnya, Anda juga dapat melihat a
thread sedang dalam proses

perintah pidstat

Setelah alat sysstat perlu diinstal di ubuntu, pidstat dapat didukung.

sudo apt install sysstat

Pilihan

-t: Menampilkan thread yang terkait dengan proses yang ditentukan

-p: Tentukan pid proses

Contoh

Lihat thread yang terkait dengan proses 12345

sudo pidstat -t -p 12345

Lihat thread yang terkait dengan semua proses

sudo pidstat -t

Lihat utas yang terkait dengan proses 12345, keluaran setiap 1 detik

sudo pidstat -t -p 12345 1

Lihat utas yang terkait dengan semua proses, keluaran setiap 1 detik

sudo pidstat -t 1

perintah teratas

Gunakan perintah top untuk melihat thread dalam proses tertentu. Anda perlu menggunakan opsi -H yang dikombinasikan dengan -p untuk menentukan pid.

Pilihan

-H: Menampilkan informasi utas

-p: Tentukan pid proses

Contoh

Lihat thread yang terkait dengan proses 12345

sudo atas -H -p 12345

Lihat thread yang terkait dengan semua proses

sudo atas -H

perintah ps

Gunakan perintah ps yang dikombinasikan dengan opsi -T untuk melihat semua thread dalam suatu proses

Pilihan

-T: Menampilkan informasi utas

-p: Tentukan pid proses

Contoh

Lihat thread yang terkait dengan proses 12345

sudo ps -T -p 12345

Lihat thread yang terkait dengan semua proses

sudo ps -T

Skenario konkurensi umum

1. Mode multi-proses

Dalam mode multi-proses, setiap proses bertanggung jawab atas tugas yang berbeda tanpa mengganggu satu sama lain.

  • keuntungan:
    • Ruang alamat suatu proses bersifat independen. Sekali pengecualian terjadi dalam suatu proses, hal itu tidak akan memengaruhi proses lainnya.
  • kekurangan:
    • Setiap proses perlu mengalokasikan ruang memori independen. Membuat proses itu mahal dan memakan lebih banyak memori.
    • Kolaborasi antar proses dan komunikasi antar proses lebih kompleks
  • Adegan yang berlaku:
    • Banyak tugas tidak terlalu erat kaitannya, sehingga mode multi-proses dapat digunakan
    • Tidak ada ketergantungan antara tugas dan mode multi-proses dapat digunakan

2. Mode multi-utas

Dalam mode multi-threading, terdapat beberapa thread dalam suatu proses, berbagi ruang memori yang sama, dan thread dapat berkomunikasi secara langsung.

  • keuntungan:
    • Komunikasi antar thread itu sederhana
    • Beberapa thread dari proses yang sama dapat berbagi sumber daya dan meningkatkan pemanfaatan sumber daya.
  • kekurangan:
    • Thread tidak memiliki ruang alamat proses yang independen. Setelah thread utama keluar, thread lain juga akan keluar.
    • Peralihan dan penjadwalan thread memerlukan sumber daya. Terlalu banyak thread akan menghabiskan sumber daya sistem.
    • Sinkronisasi antar thread rumit dan masalah keamanan thread perlu dipertimbangkan
  • Adegan yang berlaku:
    • Ada ketergantungan antara tugas dan mode multi-threading dapat digunakan
    • Komunikasi antar tugas relatif sering dan mode multi-threading dapat digunakan.

Buat utas

1. pthread_create()

pthread_create() digunakan untuk membuat thread. Setelah pembuatan berhasil, thread mulai berjalan.
Setelah pthread_create() berhasil dipanggil, ia akan mengembalikan 0, jika tidak maka akan mengembalikan kode kesalahan.

File tajuk fungsi:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
  • 1
  • 2
  • 3
  • 4

Deskripsi Parameter:

  • thread: Penunjuk ke tipe pthread_t, digunakan untuk menyimpan ID thread.
  • attr: Atribut thread, yang dapat berupa NULL, menunjukkan bahwa atribut default digunakan.
  • start_routine: Fungsi entri thread.
  • arg: Parameter diteruskan ke fungsi entri thread.

nilai kembalian:

  • 0: Berhasil dibuat.
  • EAGAIN: Sumber daya tidak mencukupi, pembuatan thread gagal.
  • EINVAL: Parameternya tidak valid.
  • ENOMEM: Memori tidak mencukupi, pembuatan thread gagal.

Melihat:

  • Setelah thread anak berhasil dibuat, thread tersebut akan dijadwalkan secara independen untuk dieksekusi dan dieksekusi secara bersamaan dengan thread lainnya.
  • Pustaka -lpthread perlu ditautkan saat kompilasi.

Contoh: Buat thread

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("%sn",(char *)arg);
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //@param tid 线程ID
    //@param attr 线程属性
    //@param start_routine 线程函数
    //@param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    sleep(1); // 等待线程执行完毕
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2. pthread_exit() keluar dari thread

pthread_exit() digunakan untuk keluar dari thread. Setelah thread selesai dieksekusi, pthread_exit() akan otomatis dipanggil untuk keluar.

File tajuk fungsi:

#include <pthread.h>

void pthread_exit(void *retval);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • retval: Nilai yang dikembalikan saat thread keluar.
  • Setelah fungsi thread dijalankan, pthread_exit() akan otomatis dipanggil untuk keluar.

3. pthread_join() menunggu thread berakhir

pthread_join() digunakan untuk menunggu thread berakhir,
Setelah memanggil pthread_join(), thread saat ini akan diblokir hingga thread berakhir.

File tajuk fungsi:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • utas: ID utas.
  • retval: Penunjuk ke nilai pengembalian thread, digunakan untuk menyimpan nilai yang dikembalikan saat thread keluar. (penunjuk sekunder)

nilai kembalian:

  • 0: Tunggu kesuksesan.
  • EINVAL: Parameternya tidak valid.
  • ESRCH: ID thread tidak ada.
  • EDEADLK: Thread berada dalam kondisi buntu.

Contoh:

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, NULL);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
等待线程结束...
Hello, World!
  • 1
  • 2

Pemisahan benang

Utas dibagi menjadi dapat digabungkan dan dilepas

  • Dapat digabungkan
    • Sebuah thread yang dapat digabungkan dapat mengambil kembali sumber dayanya dan dimatikan oleh thread lain; sumber daya memorinya (seperti tumpukan) tidak akan dilepaskan hingga diambil kembali oleh thread lain.
    • Status default pembuatan thread dapat digabungkan. Fungsi pthread_join dapat dipanggil oleh thread lain untuk menunggu thread turunan keluar dan melepaskan sumber daya terkait.
  • dapat dipisahkan
    • Itu tidak dapat didaur ulang atau dimatikan oleh thread lain. Sumber daya thread dilepaskan oleh sistem ketika thread tersebut dihentikan.
// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    //pthread_join(tid, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行

    //线程分离
    pthread_detach(tid); //! 分离线程,不用等待线程结束后才退出程序,该线程的资源在它终止时由系统来释放。

    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

Buat banyak utas

Contoh 1: Buat beberapa thread untuk melakukan tugas berbeda

// todo : 创建多个线程,执行不同的任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello_A(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}
// 线程函数
//@param arg 线程函数参数
void * print_hello_B(void *arg) {
    sleep(2); // 休眠2秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}


int main() {
    pthread_t tidA; //? 存储线程ID  typedef unsigned long int pthread_t;
    pthread_t tidB;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tidA, NULL,print_hello_A, "A_ Hello, World!");
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    int retB = pthread_create(&tidB, NULL,print_hello_B, "B_ Hello, World!");
    if (retB!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    
    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tidA, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tidB, NULL);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

Contoh 2: Buat beberapa thread untuk melakukan tugas yang sama

// todo : 创建多个线程,执行相同任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//? 两个线程执行相同任务,对函数中的值修改了,会不会影响到其他线程的执行?
//! 在多线程编程中,如果多个线程执行相同的任务并且对共享资源进行修改,可能会影响到其他线程的执行。
//! 这是因为多个线程共享相同的内存空间,对共享资源的修改可能会导致竞态条件(race condition),
//! 从而导致不可预测的行为。
//! print_hello函数中的变量i是局部变量,每个线程都会有自己的i副本,因此对i的修改不会影响到其他线程。
//! 但是,如果涉及到共享资源(例如全局变量或静态变量),就需要考虑线程同步的问题,以避免竞态条件。


//*局部变量:每个线程都有自己的栈空间,因此局部变量是线程私有的,不会影响到其他线程。
//*共享资源:如果多个线程访问和修改同一个全局变量或静态变量,就需要使用同步机制(如互斥锁、信号量等)来确保线程安全。
//Linux:在Linux系统中,默认的线程栈大小通常是8MB。可以使用ulimit -s命令查看和修改当前用户的线程栈大小。例如,ulimit -s 1024将线程栈大小设置为1MB。
//Windows:在Windows系统中,默认的线程栈大小是1MB。可以通过编译器选项或在创建线程时指定栈大小来修改。

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {

    for (char i = 'a'; i < 'z' ; ++i) {
        printf("%cn", i);
        sleep(1); // 休眠1秒
    }
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid[2]={0}; //? 存储线程ID的数组  typedef unsigned long int pthread_t;


    for (int i = 0; i < 2; ++i) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, NULL);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0], NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1], NULL);


    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

Komunikasi antar thread

Komunikasi antar proses lainnya juga berlaku untuk komunikasi antar thread.

Thread utama meneruskan parameter ke thread anak

Saat membuat thread anak melalui fungsi pthread_create(), parameter keempat pthread_create() adalah parameter yang diteruskan ke fungsi thread anak.

Subthread meneruskan parameter ke thread utama

Saat keluar dari thread anak melalui fungsi pthread_exit(), Anda dapat meneruskan parameter ke thread utama.

void pth_exit(void *retval);
  • 1

Saat menunggu akhir sub-utas melalui fungsi pthread_join(), dapatkan parameter kembalian sub-utas.

int pthread_join (pthread_t __th, void **__thread_return);
//二级指针获取到了pthread_exit()函数参数指针的指向地址,通过该地址可以获取到子线程的返回参数。
  • 1
  • 2

Contoh:

// todo : 线程直接通讯,子线程向父线程传参
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始,结束之时传递参数100的地址n");

    sleep(1); // 休眠1秒
    //! int num=100;//局部变量,函数结束释放内存
    static int num=100;//* 静态局部变量,函数结束不释放内存,延长生命周期
    pthread_exit(&num); // 线程退出
}



int main() {
    pthread_t tid; //? 存储线程ID  typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tid, NULL,print_hello, NULL);
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }



    printf("等待线程结束...n");
    void* num;//获取子进程传递的参数,num指向了子进程传递的参数
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, (void **)&num);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    printf("子线程结束,传递的参数为%dn",*(int*)num);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

benang muteks

benang muteks

Mutex adalah mekanisme sinkronisasi yang digunakan untuk mengontrol akses ke sumber daya bersama.

Keuntungan utama thread adalah kemampuannya untuk berbagi informasi melalui variabel global, namun kemudahan berbagi ini memiliki konsekuensi:

Harus dipastikan bahwa beberapa thread tidak mengubah variabel yang sama secara bersamaan

Thread tertentu tidak akan membaca variabel yang dimodifikasi oleh thread lain. Faktanya, dua thread tidak dapat mengakses bagian kritis secara bersamaan.

Prinsip kunci mutex

Prinsip dari kunci mutex adalah ketika sebuah thread mencoba memasuki area mutex, jika area mutex tersebut sudah ditempati oleh thread lain, maka thread tersebut akan diblokir hingga area mutex tersebut terlepas.

Ini pada dasarnya adalah variabel bertipe pthread_mutex_t, yang berisi nilai integer untuk mewakili status area mutex.
Jika nilainya 1, berarti sumber daya kritis saat ini dapat bersaing untuk mendapatkan akses, dan thread yang memperoleh kunci mutex dapat memasuki bagian kritis. Saat ini nilainya 0 dan thread lainnya hanya bisa menunggu.
Jika nilainya 0 berarti sumber daya kritis saat ini ditempati oleh thread lain dan tidak dapat masuk ke bagian kritis dan hanya bisa menunggu.

Karakteristik kunci mutex

typedef union
{
  struct __pthread_mutex_s __data; // 互斥锁的结构体
  char __size[__SIZEOF_PTHREAD_MUTEX_T];// 互斥锁的大小
  long int __align;// 互斥锁的对齐
} pthread_mutex_t;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • Kunci mutex adalah variabel tipe pthread_mutex_t, yang mewakili kunci mutex.
  • Jika dua thread mengakses variabel pthread_mutex_t yang sama, maka keduanya mengakses kunci mutex yang sama
  • Variabel terkait didefinisikan dalam file header pthreadtypes.h, yang merupakan gabungan yang berisi struktur.

Penggunaan kunci mutex

Ada dua cara utama untuk menginisialisasi kunci mutex thread:

inisialisasi statis

  • Tentukan variabel bertipe pthread_mutex_t dan inisialisasi ke PTHREAD_MUTEX_INITIALIZER.
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
  • 1

inisialisasi dinamis

Inisialisasi dinamis Inisialisasi dinamis terutama melibatkan dua fungsi: fungsi pthread_mutex_init dan fungsi pthread_mutex_destroy

fungsi pthread_mutex_init()

Digunakan untuk menginisialisasi kunci mutex, ia menerima dua parameter: alamat kunci mutex dan atribut kunci mutex.

File tajuk fungsi:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • mutex: Penunjuk ke tipe pthread_mutex_t, digunakan untuk menyimpan alamat kunci mutex.
  • attr: atribut kunci mutex, yang dapat berupa NULL, menunjukkan bahwa atribut default digunakan.

nilai kembalian:

  • 0: Inisialisasi berhasil.
  • Kode kesalahan dikembalikan jika gagal.
fungsi pthread_mutex_destroy()

Digunakan untuk menghancurkan kunci mutex, ia menerima satu parameter: alamat kunci mutex.

File tajuk fungsi:

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • mutex: Penunjuk ke tipe pthread_mutex_t, digunakan untuk menyimpan alamat kunci mutex.

nilai kembalian:

  • 0: Penghancuran berhasil.
  • Kode kesalahan dikembalikan jika gagal.

Contoh:

// todo :  互斥锁;创建两个线程,分别对全局变量进⾏ +1 操作
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>


static int global = 0;// 全局变量

//静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
//动态初始化互斥锁
pthread_mutex_t mut;// 互斥锁

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始n");

    int loops = *(int *)arg;
    int i,tmp = 0;
    for (i = 0;i < loops;i++){
        pthread_mutex_lock(&mut);// 加锁
        printf("子线程%d,global=%dn",i,global);
        tmp = global;
        tmp++;
        global = tmp;
        pthread_mutex_unlock(&mut);// 解锁
    }
    printf("子线程结束n");
    pthread_exit(NULL); // 线程退出
}



int main() {

    // 动态初始化互斥锁
    //* @param mutex 互斥锁
    //* @param attr 互斥锁属性 NULL 是默认属性
    int r= pthread_mutex_init(&mut,NULL);
    if (r!= 0){
        printf("pthread_mutex_init error!n");
        return 1;
    }

    pthread_t tid[2]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arg=20;
    for (int i = 0; i < 2; i++){
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, &arg);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }




    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );

    printf("%dn",global);
    printf("主线程结束n");

    // 销毁动态创建的互斥锁
    //* @param mutex 互斥锁
    pthread_mutex_destroy(&mut);// 销毁互斥锁

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

Sinkronisasi utas

Sinkronisasi thread: mengacu pada akses teratur terhadap sumber daya oleh pengunjung melalui mekanisme lain berdasarkan saling pengecualian (dalam banyak kasus).

Variabel kondisi: Mekanisme yang disediakan oleh pustaka thread khusus untuk sinkronisasi thread.

Skenario aplikasi tipikal untuk sinkronisasi thread adalah antara produsen dan konsumen.

Masalah produsen dan konsumen

Dalam model ini, dibagi menjadi thread produsen dan thread konsumen, dan proses sinkronisasi beberapa thread disimulasikan melalui thread ini.

Dalam model ini, diperlukan komponen-komponen berikut:

  • Gudang: digunakan untuk menyimpan produk, umumnya sebagai sumber daya bersama
  • Benang produsen: digunakan untuk menghasilkan produk
  • Benang konsumen: digunakan untuk mengkonsumsi produk

prinsip:

Ketika tidak ada produk di gudang, konsumen harus menunggu sampai ada produk sebelum dapat mengkonsumsinya.

Ketika gudang sudah penuh dengan produk, maka thread produsen harus menunggu hingga thread konsumen mengkonsumsi produk tersebut.

Contoh penerapan model produsen dan konsumen berdasarkan kunci mutex

Pelaku utamanya adalah konsumen

n sub-thread sebagai produser

// todo :  基于互斥锁实现⽣产者与消费者模型主线程为消费者,n 个⼦线程作为⽣产者
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static int n = 0; // 产品数量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁

//生产者执行函数
void * dofunc(void *arg) {
    int arg1 = *(int*)arg;
    for (int i = 0; i <arg1; i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        //生产产品
        printf("生产者%ld生产了%d个产品n",pthread_self(),++n);//! pthread_self()返回当前线程ID
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }
    pthread_exit(NULL);
}


int main() {
    pthread_t tid[4]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arr[4]={1,2,3,4};
    for (int i = 0; i < 4; i++) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,dofunc,&arr[i] );
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }
    //消费者执行

    for (int i = 0;i<10;i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        while (n > 0){
            //消费产品
            printf("消费者%ld消费了1个产品:%dn",pthread_self(),n--);
        }
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }


    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );
    pthread_join(tid[2],NULL );
    pthread_join(tid[3],NULL );

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

variabel kondisi

Variabel kondisi adalah mekanisme sinkronisasi yang memungkinkan thread menunggu hingga kondisi tertentu dipenuhi sebelum melanjutkan berjalan.

Prinsip dari variabel kondisi adalah berisi kunci mutex dan antrian tunggu.

Kunci mutex digunakan untuk melindungi antrian tunggu dan variabel kondisi.

Masukkan deskripsi gambar di sini

Inisialisasi variabel kondisi

Sifat variabel kondisi adalah tipe pthread_cond_t

其他线程可以阻塞在这个条件变量上, 或者唤
醒阻塞在这个条件变量上的线程
typedef union
{
  struct __pthread_cond_s __data;
  char __size[__SIZEOF_PTHREAD_COND_T];
  __extension__ long long int __align;
} pthread_cond_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Inisialisasi variabel kondisi dibagi menjadi inisialisasi statis dan inisialisasi dinamis.

inisialisasi statis

Untuk variabel kondisi yang diinisialisasi secara statis, Anda harus terlebih dahulu mendefinisikan variabel bertipe pthread_cond_t, lalu menginisialisasinya ke PTHREAD_COND_INITIALIZER.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 1

Inisialisasi dinamis pthread_cond_init()

Untuk menginisialisasi variabel kondisi secara dinamis, Anda perlu mendefinisikan variabel bertipe pthread_cond_t terlebih dahulu, lalu memanggil fungsi pthread_cond_init untuk menginisialisasinya.

File tajuk fungsi:

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • cond: Penunjuk ke tipe pthread_cond_t, digunakan untuk menyimpan alamat variabel kondisi.
  • attr: atribut variabel kondisi, yang dapat berupa NULL untuk menggunakan atribut default.

nilai kembalian:

  • 0: Inisialisasi berhasil.
  • Kode kesalahan dikembalikan jika gagal.

pthread_cond_hancurkan()

Digunakan untuk menghancurkan variabel kondisi, ia menerima satu parameter: alamat variabel kondisi.

File tajuk fungsi:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • cond: Penunjuk ke tipe pthread_cond_t, digunakan untuk menyimpan alamat variabel kondisi.

nilai kembalian:

  • 0: Penghancuran berhasil.
  • Kode kesalahan dikembalikan jika gagal.

Penggunaan variabel kondisi

Penggunaan variabel kondisi dibagi menjadi menunggu dan pemberitahuan

Tunggu pthread_cond_wait()

Fungsi menunggu pthread_cond_wait() menerima tiga parameter: alamat variabel kondisi, alamat kunci mutex, dan waktu tunggu.

File tajuk fungsi:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • cond: Penunjuk ke tipe pthread_cond_t, digunakan untuk menyimpan alamat variabel kondisi.
  • mutex: Penunjuk ke tipe pthread_mutex_t, digunakan untuk menyimpan alamat kunci mutex.
  • abstime: batas waktu, yang dapat bernilai NULL, menunjukkan tidak ada batas waktu.

nilai kembalian:

  • 0: Tunggu kesuksesan.
  • Kode kesalahan dikembalikan jika gagal.

Beritahu pthread_cond_signal()

fungsi notifikasi
pthread_cond_signal() menerima satu parameter: alamat variabel kondisi.

File tajuk fungsi:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • cond: Penunjuk ke tipe pthread_cond_t, digunakan untuk menyimpan alamat variabel kondisi.

nilai kembalian:

  • 0: Pemberitahuan berhasil.
  • Kode kesalahan dikembalikan jika gagal.

Beritahu semua pthread_cond_broadcast()

Beritahu semua fungsi
pthread_cond_broadcast() menerima satu parameter: alamat variabel kondisi.

File tajuk fungsi:

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Deskripsi Parameter:

  • cond: Penunjuk ke tipe pthread_cond_t, digunakan untuk menyimpan alamat variabel kondisi.

nilai kembalian:

  • 0: Pemberitahuan berhasil.
  • Kode kesalahan dikembalikan jika gagal.

Contoh penerapan model produsen dan konsumen berdasarkan variabel kondisional

Masukkan deskripsi gambar di sini

step 1 : 消费者线程判断消费条件是否满足 (仓库是否有产品),如果有产品可以消费,则可以正
常消费产品,然后解锁
step 2 : 当条件不能满足时 (仓库产品数量为 0),则调用 pthread_cond_wait 函数, 这个函数
            具体做的事情如下:
            在线程睡眠之前,对互斥锁解锁
            让线程进⼊到睡眠状态
            等待条件变量收到信号时 唤醒,该函数重新竞争锁,并获取锁后,函数返回 
step 3 :重新判断条件是否满足, 如果不满足,则继续调用 pthread_cond_wait 函数
step 4 : 唤醒后,从 pthread_cond_wait 返回,消费条件满足,则正常消费产品
step 5 : 释放锁,整个过程结束
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Mengapa variabel kondisi perlu digunakan bersama dengan kunci mutex?

Lindungi data bersama:

Kunci mutex digunakan untuk melindungi data bersama dan memastikan bahwa hanya satu thread yang dapat mengakses dan mengubah data pada saat yang bersamaan.

Hal ini untuk menghindari perlombaan dan inkonsistensi data.

Variabel kondisi digunakan untuk komunikasi antar thread untuk memberi tahu thread lain bahwa kondisi tertentu telah terpenuhi.

Namun, pengoperasian variabel kondisi dengan sendirinya tidak memberikan perlindungan bagi data bersama, sehingga perlu digunakan bersama dengan kunci mutex.

Hindari bangun yang salah:

Salah satu karakteristik dari variabel kondisi adalah bahwa kebangkitan palsu dapat terjadi.

Artinya, thread dibangunkan tanpa pemberitahuan eksplisit. Untuk menghindari operasi yang salah yang disebabkan oleh situasi ini,

Thread perlu dicek ulang apakah kondisinya benar-benar terpenuhi setelah bangun tidur.

Menggunakan mutex memastikan bahwa data yang dibagikan tidak diubah oleh thread lain saat memeriksa kondisi, sehingga menghindari kesalahan yang disebabkan oleh bangun palsu.

Pastikan notifikasinya benar:

Ketika sebuah thread memberi tahu thread lain melalui variabel kondisi, thread tersebut perlu memastikan bahwa data yang dibagikan telah diperbarui sebelum memberi tahu.

Mutex menjamin hal ini, memastikan bahwa semua operasi pembaruan data telah selesai sebelum melepaskan kunci.

Demikian pula, thread yang menerima notifikasi perlu menahan mutex sebelum memeriksa kondisi untuk memastikan bahwa data stabil ketika kondisi diperiksa.

Menerapkan pola sinkronisasi yang kompleks:
Menggabungkan kunci mutex dengan variabel kondisi dapat menerapkan pola sinkronisasi yang lebih kompleks, seperti masalah produsen-konsumen, masalah pembaca-penulis, dll. Mutex melindungi data bersama, dan variabel kondisi digunakan untuk koordinasi dan komunikasi antar thread.

// todo :  条件变量
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>


static int number = 0;// 产品数量
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 条件变量

// 线程函数
//@param arg 线程函数参数
void * thread_handler(void *arg) {
    int cnt = atoi((char *)arg);// 获取线程参数
    int i,tmp;// 临时变量
    for(i = 0;i < cnt;i++){// 生产产品
        pthread_mutex_lock(&mtx);// 上锁
        printf("线程 [%ld] ⽣产⼀个产品,产品数量为:%dn",pthread_self(),++number);
        pthread_mutex_unlock(&mtx);// 解锁

        //! 唤醒cond阻塞的线程
        //! @param cond 条件变量
        //pthread_cond_signal(&cond);//! 只能唤醒一个线程,如果有多个线程在等待,则只有一个线程会被唤醒
        //唤醒所有线程
        pthread_cond_broadcast(&cond);
    }
    pthread_exit((void *)0);// 线程退出
}



int main(int argc,char *argv[]) {

    pthread_t tid[argc-1];// 线程ID
    int i;
    int err;
    int total_of_produce = 0;// 总共生产的产品数量
    int total_of_consume = 0;// 总共消费的产品数量
    bool done = false;// 是否完成生产
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127