Ας υποθέσουμε ότι υπάρχει μια κλάση που περιέχει ένα δημόσιο πεδίο μετρητή int στο οποίο μπορείτε να έχετε πρόσβαση από πολλά νήματα και αυτός ο αριθμός θα αυξηθεί ή θα μειωθεί μόνο.
Κατά την προσθήκη αυτού του πεδίου, ποιο από τα παρακάτω σχήματα πρέπει να χρησιμοποιηθεί και γιατί;
- lock(this.locker) this.counter++;
- Interlocked.Increment(ref this.counter);
- Αλλάξτε τον τροποποιητή πρόσβασης του μετρητή σε δημόσιο πτητικό
Το χειρότερο (κανένα από αυτά δεν λειτουργεί πραγματικά)
Αλλάξτε τον τροποποιητή πρόσβασης του μετρητή σε δημόσιο πτητικό
Αυτή η προσέγγιση στην πραγματικότητα δεν είναι καθόλου ασφαλής και το σημείο σχετικά με το πτητικό είναι ότι πολλαπλά νήματα που εκτελούνται σε πολλαπλές CPU αποθηκεύουν δεδομένα προσωρινής αποθήκευσης και αναδιατάσσουν τις εκτελούμενες εντολές.
Εάν είναι μη πτητική, όταν η CPU A αυξάνεται κατά μια τιμή, η CPU B πρέπει να περιμένει λίγο για να δει την αυξημένη τιμή, κάτι που μπορεί να οδηγήσει σε προβλήματα.
Εάν είναι πτητικό, διασφαλίζει ότι και οι δύο CPU βλέπουν την ίδια τιμή ταυτόχρονα. Αλλά δεν αποφεύγει τις οριζόντιες λειτουργίες ανάγνωσης και εγγραφής.
Η προσθήκη τιμής σε μια μεταβλητή απαιτεί στην πραγματικότητα τρία βήματα
1. Ανάγνωση, 2. Προσθήκη 3. Γράψτε
Ας υποθέσουμε ότι το νήμα Α διαβάζει την τιμή του μετρητή ως 1 και δεν είναι έτοιμο να αυξηθεί, τότε το νήμα Β διαβάζει επίσης την τιμή του μετρητή ως 1 και, στη συνέχεια, και τα δύο νήματα αρχίζουν να εκτελούν αυξητικές λειτουργίες και λειτουργίες εγγραφής. Η τιμή του τελικού μετρητή είναι 2. Αυτό δεν είναι σωστό, και τα δύο νήματα έχουν κάνει μια λειτουργία αύξησης και το σωστό αποτέλεσμα θα πρέπει να είναι 3. Επομένως, η επισήμανση του ως ασταθούς είναι απλώς επικίνδυνος.
Είναι καλύτερα
lock(this.locker) this.counter++;
Με αυτόν τον τρόπο είναι ασφαλές (θυμηθείτε να κλειδώσετε οπουδήποτε θέλετε να αποκτήσετε πρόσβαση σε αυτό.counter, φυσικά). Αποτρέπει οποιοδήποτε άλλο νήμα από την εκτέλεση του κλειδωμένου κώδικα. Και επίσης αποτρέπει το πρόβλημα αλληλουχίας εντολών πολλαπλών CPU που αναφέρθηκε παραπάνω. Το πρόβλημα είναι ότι η κλειδαριά είναι αργή στην απόδοση και εάν χρησιμοποιείτε κλειδαριά σε άλλα άσχετα μέρη, μπορεί να μπλοκάρει τα άλλα νήματα σας.
Καλύτερο
Interlocked.Increment(ref this.counter);
Αυτό είναι ασφαλές και πολύ αποτελεσματικό. Εκτελεί ανάγνωση, αύξηση, εγγραφή τριών πράξεων σε ένα άτομο χωρίς να διακόπτεται στη μέση. Επειδή δεν επηρεάζει άλλο κώδικα, δεν χρειάζεται να θυμάστε κλειδαριές αλλού. Και είναι επίσης πολύ γρήγορο (όπως λέει το MSDN, στις σημερινές CPU, είναι συχνά απλώς μια οδηγία).
Αλλά δεν είμαι απολύτως σίγουρος αν μπορεί επίσης να λύσει το πρόβλημα παραγγελίας εντολών της CPU ή αν πρέπει να χρησιμοποιηθεί σε συνδυασμό με το πτητικό και αυτήν την αύξηση.
Συμπλήρωμα: Ποια προβλήματα λύνει καλά το πτητικό;
Δεδομένου ότι το πτητικό δεν μπορεί να αποτρέψει το multithreading, τι μπορεί να κάνει; Ένα καλό παράδειγμα είναι ότι έχετε δύο νήματα, το ένα γράφει πάντα σε μια μεταβλητή, ας υποθέσουμε ότι αυτή η μεταβλητή είναι queneLength και η άλλη διαβάζει πάντα δεδομένα από αυτήν τη μεταβλητή. Εάν το queueLenght δεν είναι πτητικό, το νήμα Α μπορεί να διαβαστεί 5 φορές, αλλά το νήμα Β μπορεί να δει καθυστερημένα δεδομένα ή ακόμα και δεδομένα με λάθος σειρά. Μια λύση είναι να χρησιμοποιήσετε κλειδαριά, αλλά σε αυτήν την περίπτωση μπορείτε επίσης να χρησιμοποιήσετε πτητικό. Αυτό διασφαλίζει ότι το νήμα Β βλέπει πάντα τα πιο πρόσφατα δεδομένα που γράφτηκαν από το νήμα Α, αλλά αυτή η λογική λειτουργεί μόνο αν δεν το διαβάσετε όταν το γράφετε και αν δεν το γράψετε όταν το διαβάζετε. Μόλις πολλά νήματα θέλουν να κάνουν λειτουργίες ανάγνωσης-τροποποίησης-εγγραφής, πρέπει να χρησιμοποιήσετε το Interlocked ή το κλείδωμα.
|