Κοινή χρήση τεχνολογίας

Linux πολλαπλών διεργασιών και πολλαπλών νημάτων (8) Multi-threading

2024-07-12

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

Multithreading

Ορισμός νήματος

Ένα νήμα είναι μια μονάδα εκτέλεσης σε μια διαδικασία.

Υπεύθυνος για την εκτέλεση προγραμμάτων στην τρέχουσα διαδικασία,

Υπάρχει τουλάχιστον ένα νήμα σε μια διαδικασία

Μπορεί να υπάρχουν πολλά νήματα σε μια διαδικασία

Πολλαπλά νήματα μοιράζονται όλους τους πόρους της ίδιας διαδικασίας και κάθε νήμα συμμετέχει στον ενοποιημένο προγραμματισμό του λειτουργικού συστήματος

Μπορεί απλά να κατανοηθεί ως διεργασία = πόροι μνήμης + κύριο νήμα + θυγατρικό νήμα +…

Νήματα και διαδικασίες

Για εργασίες που είναι στενά συνδεδεμένες, προτιμάται το multi-threading κατά τη διάρκεια της ταυτόχρονης λειτουργίας.

  • Διαδικασία: Η βασική μονάδα κατανομής πόρων από το λειτουργικό σύστημα Είναι η μικρότερη μονάδα κατανομής πόρων, η μονάδα εκτέλεσης και προγραμματισμού του προγράμματος και η εκτέλεση του προγράμματος.
  • Thread: Είναι η βασική μονάδα προγραμματισμού και αποστολής της CPU, η μικρότερη μονάδα εκτέλεσης της CPU, η μικρότερη μονάδα ροής εκτέλεσης προγράμματος και η μικρότερη μονάδα εκτέλεσης προγράμματος.

Η διαφορά μεταξύ νημάτων και διεργασιών:

  • χώρο μνήμης
    • Πολλά νήματα σε μια διεργασία μοιράζονται τον ίδιο χώρο μνήμης
    • Πολλαπλές διεργασίες έχουν ανεξάρτητο χώρο μνήμης
  • Επικοινωνία μεταξύ διεργασιών/νημάτων
    • Απλή επικοινωνία μεταξύ των νημάτων
    • Η επικοινωνία μεταξύ των διαδικασιών είναι πολύπλοκη

Πόροι νημάτων

  • Κοινόχρηστοι πόροι διαδικασίας
    • τον ίδιο χώρο διευθύνσεων
    • πίνακας περιγραφής αρχείου
    • Πώς γίνεται η επεξεργασία κάθε σήματος
    • τρέχον κατάλογο εργασίας
    • αναγνωριστικό χρήστη και αναγνωριστικό ομάδας
  • Μοναδικοί πόροι
    • Στοίβα νημάτων
    • Κάθε νήμα έχει ιδιωτικές πληροφορίες περιβάλλοντος
    • Αναγνωριστικό νήματος
    • Καταχώριση αξίας
    • λανθασμένη τιμή
    • Λέξεις μάσκας σήματος και προτεραιότητες προγραμματισμού

Εντολές που σχετίζονται με το νήμα

Υπάρχουν πολλές εντολές για την προβολή της διαδικασίας στο σύστημα Linux, όπως pidstat, top, ps, μπορείτε να δείτε τη διαδικασία, μπορείτε επίσης να προβάλετε
νήμα υπό επεξεργασία

εντολή pidstat

Αφού πρέπει να εγκατασταθεί το εργαλείο sysstat στο ubuntu, μπορεί να υποστηριχθεί το pidstat.

sudo apt εγκατάσταση sysstat

Επιλογές

-t: Εμφάνιση των νημάτων που σχετίζονται με την καθορισμένη διαδικασία

-p: Καθορίστε τη διαδικασία pid

Παράδειγμα

Δείτε τα νήματα που σχετίζονται με τη διαδικασία 12345

sudo pidstat -t -p 12345

Προβολή νημάτων που σχετίζονται με όλες τις διαδικασίες

sudo pidstat -τ

Προβάλετε τα νήματα που σχετίζονται με τη διεργασία 12345, με έξοδο κάθε 1 δευτερόλεπτο

sudo pidstat -t -p 12345 1

Δείτε τα νήματα που σχετίζονται με όλες τις διεργασίες, με έξοδο κάθε 1 δευτερόλεπτο

sudo pidstat -t 1

κορυφαία εντολή

Χρησιμοποιήστε την επάνω εντολή για να δείτε τα νήματα σε μια συγκεκριμένη διαδικασία Πρέπει να χρησιμοποιήσετε την επιλογή -H σε συνδυασμό με -p για να καθορίσετε το pid.

Επιλογές

-H: Εμφάνιση πληροφοριών νήματος

-p: Καθορίστε τη διαδικασία pid

Παράδειγμα

Δείτε τα νήματα που σχετίζονται με τη διαδικασία 12345

sudo top -H -p 12345

Προβολή νημάτων που σχετίζονται με όλες τις διαδικασίες

sudo top -H

εντολή ps

Χρησιμοποιήστε την εντολή ps σε συνδυασμό με την επιλογή -T για να δείτε όλα τα νήματα σε μια διεργασία

Επιλογές

-T: Εμφάνιση πληροφοριών νήματος

-p: Καθορίστε τη διαδικασία pid

Παράδειγμα

Δείτε τα νήματα που σχετίζονται με τη διαδικασία 12345

sudo ps -T -p 12345

Προβολή νημάτων που σχετίζονται με όλες τις διαδικασίες

sudo ps -T

Κοινά σενάρια συγχρονισμού

1. Λειτουργία πολλαπλών διεργασιών

Στη λειτουργία πολλαπλών διεργασιών, κάθε διεργασία είναι υπεύθυνη για διαφορετικές εργασίες χωρίς παρεμβολές μεταξύ τους. Κάθε διεργασία εκτελείται σε διαφορετικό χώρο μνήμης χωρίς να επηρεάζει η μία την άλλη.

  • πλεονέκτημα:
    • Ο χώρος διευθύνσεων της διεργασίας είναι ανεξάρτητος Μόλις παρουσιαστεί μια εξαίρεση σε μια διεργασία, δεν θα επηρεάσει άλλες διεργασίες.
  • έλλειψη:
    • Κάθε διεργασία χρειάζεται να εκχωρεί ανεξάρτητο χώρο στη μνήμη Η δημιουργία μιας διεργασίας είναι δαπανηρή και καταλαμβάνει περισσότερη μνήμη.
    • Η συνεργασία μεταξύ των διαδικασιών και η επικοινωνία μεταξύ των διαδικασιών είναι πιο περίπλοκες
  • Εφαρμόσιμη σκηνή:
    • Πολλαπλές εργασίες δεν συνδέονται πολύ στενά, επομένως μπορεί να χρησιμοποιηθεί η λειτουργία πολλαπλών διεργασιών
    • Δεν υπάρχουν εξαρτήσεις μεταξύ των εργασιών και μπορεί να χρησιμοποιηθεί η λειτουργία πολλαπλών διεργασιών

2. Λειτουργία πολλαπλών νημάτων

Στη λειτουργία πολλαπλών νημάτων, μπορεί να υπάρχουν πολλά νήματα σε μια διεργασία, που μοιράζονται τον ίδιο χώρο μνήμης και τα νήματα μπορούν να επικοινωνούν απευθείας.

  • πλεονέκτημα:
    • Η επικοινωνία μεταξύ των νημάτων είναι απλή
    • Πολλαπλά νήματα της ίδιας διαδικασίας μπορούν να μοιράζονται πόρους και να βελτιώσουν τη χρήση πόρων.
  • έλλειψη:
    • Τα νήματα δεν έχουν ανεξάρτητους χώρους διευθύνσεων διεργασίας Μετά την έξοδο από το κύριο νήμα, θα εξέλθουν και άλλα νήματα.
    • Η εναλλαγή νημάτων και ο προγραμματισμός απαιτούν πόρους.
    • Ο συγχρονισμός μεταξύ των νημάτων είναι πολύπλοκος και πρέπει να ληφθούν υπόψη ζητήματα ασφάλειας νημάτων
  • Εφαρμόσιμη σκηνή:
    • Υπάρχουν εξαρτήσεις μεταξύ των εργασιών και μπορεί να χρησιμοποιηθεί η λειτουργία πολλαπλών νημάτων
    • Η επικοινωνία μεταξύ των εργασιών είναι σχετικά συχνή και μπορεί να χρησιμοποιηθεί η λειτουργία πολλαπλών νημάτων.

Δημιουργία νήματος

1. pthread_create()

Η pthread_create() χρησιμοποιείται για τη δημιουργία ενός νήματος Αφού η δημιουργία είναι επιτυχής, το νήμα αρχίζει να εκτελείται.
Μετά την επιτυχή κλήση της pthread_create(), θα επιστρέψει 0, διαφορετικά θα επιστρέψει έναν κωδικό σφάλματος.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • νήμα: δείκτης προς τον τύπο pthread_t, που χρησιμοποιείται για την αποθήκευση του αναγνωριστικού νήματος.
  • attr: Χαρακτηριστικό νήματος, το οποίο μπορεί να είναι NULL, υποδεικνύοντας ότι χρησιμοποιείται το προεπιλεγμένο χαρακτηριστικό.
  • start_routine: Συνάρτηση εισαγωγής του νήματος.
  • arg: Παράμετροι που μεταβιβάζονται στη συνάρτηση εισαγωγής νήματος.

επιστρεφόμενη τιμή:

  • 0: Δημιουργήθηκε με επιτυχία.
  • ΞΑΝΑ: Ανεπαρκείς πόροι, η δημιουργία νήματος απέτυχε.
  • EINVAL: Η παράμετρος δεν είναι έγκυρη.
  • ENOMEM: Ανεπαρκής μνήμη, η δημιουργία νήματος απέτυχε.

Ειδοποίηση:

  • Μόλις δημιουργηθεί με επιτυχία το θυγατρικό νήμα, θα προγραμματιστεί ανεξάρτητα για εκτέλεση και θα εκτελεστεί ταυτόχρονα με άλλα νήματα.
  • Η βιβλιοθήκη -lpthread πρέπει να είναι συνδεδεμένη κατά τη μεταγλώττιση.

Παράδειγμα: Δημιουργήστε ένα νήμα

// 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() εξέρχεται από το νήμα

Η pthread_exit() χρησιμοποιείται για έξοδο από το νήμα Αφού ολοκληρωθεί η εκτέλεση του νήματος, η pthread_exit() θα κληθεί αυτόματα για έξοδο.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • retval: Η τιμή που επιστρέφεται κατά την έξοδο του νήματος.
  • Μετά την εκτέλεση της συνάρτησης νήματος, η pthread_exit() θα κληθεί αυτόματα για έξοδο.

3. Η pthread_join() περιμένει να τελειώσει το νήμα

Η pthread_join() χρησιμοποιείται για να περιμένει να τελειώσει το νήμα,
Αφού καλέσετε το pthread_join(), το τρέχον νήμα θα αποκλειστεί μέχρι να τελειώσει το νήμα.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • νήμα: Αναγνωριστικό νήματος.
  • retval: Δείκτης προς την τιμή επιστροφής νήματος, που χρησιμοποιείται για την αποθήκευση της τιμής που επιστρέφεται κατά την έξοδο του νήματος. (δευτερεύων δείκτης)

επιστρεφόμενη τιμή:

  • 0: Περιμένετε την επιτυχία.
  • EINVAL: Η παράμετρος δεν είναι έγκυρη.
  • ESRCH: Το αναγνωριστικό νήματος δεν υπάρχει.
  • EDEADLK: Το νήμα είναι σε αδιέξοδο.

Παράδειγμα:

// 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

Διαχωρισμός νημάτων

Τα νήματα χωρίζονται σε συνδυαστικά και αποσπώμενα

  • Μπορεί να συνδυαστεί
    • Ένα συνδυαστικό νήμα μπορεί να ανακτήσει τους πόρους του και να σκοτωθεί από άλλα νήματα οι πόροι μνήμης του (όπως η στοίβα) δεν απελευθερώνονται μέχρι να ανακτηθούν από άλλα νήματα.
    • Η προεπιλεγμένη κατάσταση δημιουργίας νήματος μπορεί να συνδυαστεί Η συνάρτηση pthread_join μπορεί να κληθεί από άλλα νήματα για να περιμένει την έξοδο του θυγατρικού νήματος και να απελευθερώσει σχετικούς πόρους.
  • διαχωριστικός
    • Δεν μπορεί να ανακυκλωθεί ή να σκοτωθεί από άλλα νήματα Οι πόροι του νήματος απελευθερώνονται από το σύστημα όταν τερματίζεται.
// 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

Δημιουργήστε πολλαπλά νήματα

Παράδειγμα 1: Δημιουργήστε πολλαπλά νήματα για να εκτελέσετε διαφορετικές εργασίες

// 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

Παράδειγμα 2: Δημιουργήστε πολλαπλά νήματα για να εκτελέσετε την ίδια εργασία

// 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

Επικοινωνία μεταξύ των νημάτων

Άλλη επικοινωνία μεταξύ διεργασιών ισχύει επίσης για την επικοινωνία μεταξύ νημάτων.

Το κύριο νήμα μεταβιβάζει παραμέτρους στο θυγατρικό νήμα

Όταν δημιουργείτε ένα θυγατρικό νήμα μέσω της συνάρτησης pthread_create(), η τέταρτη παράμετρος της pthread_create() είναι η παράμετρος που μεταβιβάζεται στη συνάρτηση του θυγατρικού νήματος.

Το Subthread μεταβιβάζει παραμέτρους στο κύριο νήμα

Κατά την έξοδο από το θυγατρικό νήμα μέσω της συνάρτησης pthread_exit(), μπορείτε να περάσετε παραμέτρους στο κύριο νήμα.

void pth_exit(void *retval);
  • 1

Όταν περιμένετε το τέλος του υπο-νήματος μέσω της συνάρτησης pthread_join(), αποκτήστε τις παραμέτρους επιστροφής του υπο-νήματος.

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

Παράδειγμα:

// 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

Κλωστή mutex

Κλωστή mutex

Το Mutex είναι ένας μηχανισμός συγχρονισμού που χρησιμοποιείται για τον έλεγχο της πρόσβασης σε κοινόχρηστους πόρους.

Το κύριο πλεονέκτημα των νημάτων είναι η δυνατότητα κοινής χρήσης πληροφοριών μέσω καθολικών μεταβλητών, αλλά αυτή η βολική κοινή χρήση έχει κόστος:

Πρέπει να διασφαλιστεί ότι πολλά νήματα δεν τροποποιούν την ίδια μεταβλητή ταυτόχρονα

Ένα νήμα δεν θα διαβάζει μεταβλητές που τροποποιούνται από άλλα νήματα Στην πραγματικότητα, δύο νήματα δεν μπορούν να έχουν πρόσβαση στο κρίσιμο τμήμα ταυτόχρονα.

Η αρχή της κλειδαριάς mutex

Η αρχή ενός κλειδώματος mutex είναι ότι όταν ένα νήμα επιχειρεί να εισέλθει σε μια περιοχή mutex, εάν η περιοχή mutex είναι ήδη κατειλημμένη από άλλα νήματα, το νήμα θα μπλοκάρεται μέχρι να απελευθερωθεί η περιοχή mutex.

Είναι ουσιαστικά μια μεταβλητή του τύπου pthread_mutex_t, η οποία περιέχει μια ακέραια τιμή για να αντιπροσωπεύει την κατάσταση της περιοχής mutex.
Όταν η τιμή είναι 1, σημαίνει ότι ο τρέχων κρίσιμος πόρος μπορεί να ανταγωνιστεί για πρόσβαση και το νήμα που αποκτά το κλείδωμα mutex μπορεί να εισέλθει στο κρίσιμο τμήμα. Αυτή τη στιγμή η τιμή είναι 0 και άλλα νήματα μπορούν μόνο να περιμένουν.
Όταν η τιμή είναι 0, σημαίνει ότι ο τρέχων κρίσιμος πόρος καταλαμβάνεται από άλλα νήματα και δεν μπορεί να εισέλθει στο κρίσιμο τμήμα και μπορεί μόνο να περιμένει.

Χαρακτηριστικά των κλειδαριών 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
  • Η κλειδαριά mutex είναι μια μεταβλητή τύπου pthread_mutex_t, η οποία αντιπροσωπεύει μια κλειδαριά mutex.
  • Εάν δύο νήματα έχουν πρόσβαση στην ίδια μεταβλητή pthread_mutex_t, τότε έχουν πρόσβαση στο ίδιο κλείδωμα mutex
  • Οι αντίστοιχες μεταβλητές ορίζονται στο αρχείο κεφαλίδας pthreadtypes.h, το οποίο είναι ένα κοινό σώμα που περιέχει μια δομή.

Χρήση κλειδαριών mutex

Υπάρχουν δύο κύριοι τρόποι για την προετοιμασία των κλειδαριών mutex νημάτων:

στατική αρχικοποίηση

  • Ορίστε μια μεταβλητή τύπου pthread_mutex_t και αρχικοποιήστε την σε PTHREAD_MUTEX_INITIALIZER.
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
  • 1

δυναμική εκκίνηση

Δυναμική εκκίνηση Η δυναμική εκκίνηση περιλαμβάνει κυρίως δύο συναρτήσεις: συνάρτηση pthread_mutex_init και συνάρτηση pthread_mutex_destroy

συνάρτηση pthread_mutex_init().

Χρησιμοποιείται για την προετοιμασία μιας κλειδαριάς mutex, δέχεται δύο παραμέτρους: τη διεύθυνση της κλειδαριάς mutex και τα χαρακτηριστικά της κλειδαριάς mutex.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • mutex: Δείκτης στον τύπο pthread_mutex_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της κλειδαριάς mutex.
  • attr: το χαρακτηριστικό του κλειδώματος mutex, το οποίο μπορεί να είναι NULL, υποδεικνύοντας ότι χρησιμοποιείται το προεπιλεγμένο χαρακτηριστικό.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής προετοιμασία.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.
συνάρτηση pthread_mutex_destroy().

Χρησιμοποιείται για την καταστροφή της κλειδαριάς mutex, δέχεται μία παράμετρο: τη διεύθυνση της κλειδαριάς mutex.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • mutex: Δείκτης στον τύπο pthread_mutex_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της κλειδαριάς mutex.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής καταστροφή.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

Παράδειγμα:

// 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

Συγχρονισμός νημάτων

Συγχρονισμός νημάτων: αναφέρεται στην εύρυθμη πρόσβαση των επισκεπτών σε πόρους μέσω άλλων μηχανισμών με βάση τον αμοιβαίο αποκλεισμό (στις περισσότερες περιπτώσεις).

Μεταβλητή συνθήκης: Ένας μηχανισμός που παρέχεται από τη βιβλιοθήκη νημάτων ειδικά για συγχρονισμό νημάτων.

Ένα τυπικό σενάριο εφαρμογής για συγχρονισμό νημάτων είναι μεταξύ παραγωγών και καταναλωτών.

Θέματα παραγωγού και καταναλωτή

Σε αυτό το μοντέλο, χωρίζεται σε νήμα παραγωγού και νήμα καταναλωτή και η διαδικασία συγχρονισμού πολλαπλών νημάτων προσομοιώνεται μέσω αυτού του νήματος.

Σε αυτό το μοντέλο απαιτούνται τα ακόλουθα εξαρτήματα:

  • Αποθήκη: χρησιμοποιείται για την αποθήκευση προϊόντων, γενικά ως κοινόχρηστος πόρος
  • Νήμα παραγωγού: χρησιμοποιείται για την παραγωγή προϊόντων
  • Καταναλωτικό νήμα: χρησιμοποιείται για την κατανάλωση προϊόντων

αρχή:

Όταν δεν υπάρχει προϊόν στην αποθήκη, το νήμα του καταναλωτή πρέπει να περιμένει μέχρι να υπάρξει ένα προϊόν για να μπορέσει να το καταναλώσει.

Όταν η αποθήκη γεμίζει με προϊόντα, το νήμα παραγωγού πρέπει να περιμένει μέχρι το νήμα του καταναλωτή να καταναλώσει τα προϊόντα.

Παράδειγμα εφαρμογής μοντέλων παραγωγού και καταναλωτή που βασίζονται σε κλειδαριές mutex

Το κύριο νήμα είναι ο καταναλωτής

n υπο-νήματα ως παραγωγοί

// 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

μεταβλητή συνθήκης

Μια μεταβλητή συνθήκης είναι ένας μηχανισμός συγχρονισμού που επιτρέπει σε ένα νήμα να περιμένει να εκπληρωθεί μια συγκεκριμένη συνθήκη πριν συνεχίσει να εκτελείται.

Η αρχή μιας μεταβλητής συνθήκης είναι ότι περιέχει μια κλειδαριά mutex και μια ουρά αναμονής.

Οι κλειδαριές Mutex χρησιμοποιούνται για την προστασία των ουρών αναμονής και των μεταβλητών συνθηκών.

Εισαγάγετε την περιγραφή της εικόνας εδώ

Αρχικοποίηση μεταβλητής συνθήκης

Η φύση της μεταβλητής συνθήκης είναι τύπου 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

Η αρχικοποίηση των μεταβλητών συνθήκης χωρίζεται σε στατική και δυναμική εκκίνηση.

στατική αρχικοποίηση

Για στατικά αρχικοποιημένες μεταβλητές συνθήκης, πρέπει πρώτα να ορίσετε μια μεταβλητή τύπου pthread_cond_t και στη συνέχεια να την αρχικοποιήσετε σε PTHREAD_COND_INITIALIZER.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 1

Δυναμική προετοιμασία pthread_cond_init()

Για να αρχικοποιήσετε δυναμικά μια μεταβλητή συνθήκης, πρέπει πρώτα να ορίσετε μια μεταβλητή τύπου pthread_cond_t και στη συνέχεια να καλέσετε τη συνάρτηση pthread_cond_init για να την αρχικοποιήσετε.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • cond: Δείκτης στον τύπο pthread_cond_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της μεταβλητής συνθήκης.
  • attr: χαρακτηριστικό της μεταβλητής συνθήκης, το οποίο μπορεί να είναι NULL για να χρησιμοποιηθεί το προεπιλεγμένο χαρακτηριστικό.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής προετοιμασία.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

pthread_cond_destroy()

Χρησιμοποιείται για την καταστροφή μεταβλητών συνθήκης, δέχεται μία παράμετρο: τη διεύθυνση της μεταβλητής συνθήκης.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • cond: Δείκτης στον τύπο pthread_cond_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της μεταβλητής συνθήκης.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής καταστροφή.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

Χρήση μεταβλητών συνθήκης

Η χρήση των μεταβλητών συνθήκης χωρίζεται σε αναμονή και ειδοποίηση

Περιμένετε για pthread_cond_wait()

Η συνάρτηση αναμονής pthread_cond_wait() δέχεται τρεις παραμέτρους: τη διεύθυνση της μεταβλητής συνθήκης, τη διεύθυνση του κλειδώματος mutex και τον χρόνο αναμονής.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • cond: Δείκτης στον τύπο pthread_cond_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της μεταβλητής συνθήκης.
  • mutex: Δείκτης στον τύπο pthread_mutex_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της κλειδαριάς mutex.
  • abstime: timeout, το οποίο μπορεί να είναι NULL, υποδεικνύοντας ότι δεν υπάρχει timeout.

επιστρεφόμενη τιμή:

  • 0: Περιμένετε την επιτυχία.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

Ειδοποίηση pthread_cond_signal()

λειτουργία ειδοποίησης
Η pthread_cond_signal() δέχεται μία παράμετρο: τη διεύθυνση της μεταβλητής συνθήκης.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • cond: Δείκτης στον τύπο pthread_cond_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της μεταβλητής συνθήκης.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής ειδοποίηση.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

Ειδοποίηση όλων των pthread_cond_broadcast()

Ειδοποίηση όλων των λειτουργιών
Η pthread_cond_broadcast() δέχεται μία παράμετρο: τη διεύθυνση της μεταβλητής συνθήκης.

Αρχείο κεφαλίδας συνάρτησης:

#include <pthread.h>

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

Περιγραφή παραμέτρου:

  • cond: Δείκτης στον τύπο pthread_cond_t, που χρησιμοποιείται για την αποθήκευση της διεύθυνσης της μεταβλητής συνθήκης.

επιστρεφόμενη τιμή:

  • 0: Επιτυχής ειδοποίηση.
  • Σε περίπτωση αποτυχίας επιστρέφεται ένας κωδικός σφάλματος.

Παράδειγμα εφαρμογής μοντέλων παραγωγού και καταναλωτή που βασίζονται σε μεταβλητές υπό όρους

Εισαγάγετε την περιγραφή της εικόνας εδώ

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

Γιατί οι μεταβλητές συνθήκης πρέπει να χρησιμοποιούνται σε συνδυασμό με κλειδαριές mutex;

Προστασία κοινόχρηστων δεδομένων:

Οι κλειδαριές Mutex χρησιμοποιούνται για την προστασία των κοινόχρηστων δεδομένων και διασφαλίζουν ότι μόνο ένα νήμα μπορεί να έχει πρόσβαση και να τροποποιεί τα δεδομένα ταυτόχρονα.

Αυτό αποφεύγει τις φυλές δεδομένων και τις ασυνέπειες.

Οι μεταβλητές συνθήκης χρησιμοποιούνται για την επικοινωνία μεταξύ των νημάτων για να ειδοποιήσουν άλλα νήματα ότι μια συγκεκριμένη συνθήκη έχει εκπληρωθεί.

Ωστόσο, η λειτουργία των μεταβλητών συνθήκης δεν παρέχει από μόνη της προστασία για τα κοινόχρηστα δεδομένα και επομένως πρέπει να χρησιμοποιείται σε συνδυασμό με ένα κλείδωμα mutex.

Αποφύγετε τις ψευδείς αφυπνίσεις:

Ένα χαρακτηριστικό των μεταβλητών συνθήκης είναι ότι μπορεί να εμφανιστεί ψευδής αφύπνιση.

Δηλαδή, το νήμα αφυπνίζεται χωρίς ρητή ειδοποίηση. Προκειμένου να αποφευχθούν λανθασμένες λειτουργίες που προκαλούνται από αυτήν την κατάσταση,

Το νήμα πρέπει να ελέγξει ξανά εάν η συνθήκη πληρούται πράγματι μετά την αφύπνιση.

Η χρήση ενός mutex διασφαλίζει ότι τα κοινόχρηστα δεδομένα δεν τροποποιούνται από άλλα νήματα κατά τον έλεγχο των συνθηκών, αποφεύγοντας έτσι σφάλματα που προκαλούνται από ψευδείς αφυπνίσεις.

Βεβαιωθείτε ότι η ειδοποίηση είναι σωστή:

Όταν ένα νήμα ειδοποιεί άλλα νήματα μέσω μεταβλητών συνθήκης, πρέπει να διασφαλίσει ότι τα κοινόχρηστα δεδομένα έχουν ενημερωθεί πριν από την ειδοποίηση.

Ένα mutex το εγγυάται αυτό, διασφαλίζοντας ότι όλες οι λειτουργίες ενημέρωσης δεδομένων έχουν ολοκληρωθεί πριν απελευθερώσετε το κλείδωμα.

Ομοίως, το νήμα που λαμβάνει την ειδοποίηση πρέπει να κρατήσει το mutex πριν ελέγξει τη συνθήκη για να διασφαλίσει ότι τα δεδομένα είναι σταθερά όταν ελέγχεται η συνθήκη.

Εφαρμογή πολύπλοκων μοτίβων συγχρονισμού:
Ο συνδυασμός κλειδαριών mutex με μεταβλητές συνθήκης μπορεί να εφαρμόσει πιο πολύπλοκα μοτίβα συγχρονισμού, όπως προβλήματα παραγωγού-καταναλωτή, προβλήματα αναγνώστη-συγγραφέα κ.λπ. Τα Mutexe προστατεύουν τα κοινόχρηστα δεδομένα και οι μεταβλητές συνθήκης χρησιμοποιούνται για τον συντονισμό και την επικοινωνία μεταξύ των νημάτων.

// 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