Dalam pengaturcaraan berbilang benang Linux, kunci adalah mekanisme yang sangat penting yang boleh mengelakkan persaingan dan kebuntuan antara utas. Walau bagaimanapun, jika kunci digunakan secara tidak betul, kemerosotan prestasi dan tingkah laku tidak menentu boleh berlaku. Artikel ini akan memperkenalkan jenis kunci biasa dalam Linux, cara menggunakannya dengan betul dan cara mengelakkan masalah seperti perbalahan dan kebuntuan.
Dalam pengaturcaraan, konsep kunci mutex objek diperkenalkan untuk memastikan integriti operasi data yang dikongsi. Setiap objek sepadan dengan tanda yang dipanggil "mutex lock", yang digunakan untuk memastikan bahawa hanya satu benang boleh mengakses objek pada bila-bila masa. Mekanisme kunci mutex yang dilaksanakan oleh Linux termasuk kunci mutex POSIX dan kunci mutex kernel Artikel ini terutamanya membincangkan tentang kunci mutex POSIX, iaitu kunci mutex antara benang.
Semaphore digunakan untuk penyegerakan berbilang utas dan berbilang tugas Apabila satu utas menyelesaikan tindakan tertentu, ia memberitahu utas lain melalui semafor, dan utas lain kemudian melakukan tindakan tertentu (apabila semua orang berada dalam sem_wait, mereka disekat di sana) . Kunci Mutex digunakan untuk pengecualian bersama berbilang utas dan berbilang tugas Jika satu utas menduduki sumber tertentu, utas lain tidak boleh mengaksesnya sehingga utas ini dibuka kuncinya, utas lain boleh mula menggunakan sumber ini. Sebagai contoh, akses kepada pembolehubah global kadangkala memerlukan penguncian dan buka kunci selepas operasi selesai. Kadangkala kunci dan semaphore digunakan pada masa yang sama”
Dalam erti kata lain, semafor tidak semestinya mengunci sumber tertentu, tetapi konsep proses Contohnya: terdapat dua utas A dan B. Benang B perlu menunggu utas A menyelesaikan tugasan tertentu sebelum meneruskan langkah berikut. . Tugas ini tidak semestinya melibatkan penguncian sumber tertentu, tetapi juga boleh melibatkan melakukan beberapa pengiraan atau pemprosesan data. Mutex benang ialah konsep "mengunci sumber tertentu" Semasa tempoh kunci, benang lain tidak boleh beroperasi pada data yang dilindungi. Dalam sesetengah kes, kedua-duanya boleh ditukar ganti.
Perbezaan antara keduanya:
Skop
Semaphore: antara-proses atau antara-benang (linux sahaja antara-benang)
Kunci mutex: antara benang
Bila dikunci
Semaphore: Selagi nilai semaphore lebih besar daripada 0, thread lain boleh sem_wait dengan jayanya Selepas berjaya, nilai semaphore dikurangkan satu. Jika nilai tidak lebih daripada 0, sem_wait blok sehingga nilai dinaikkan satu selepas sem_post dikeluarkan. Dalam satu perkataan, nilai semafor>=0.
Kunci Mutex: Selagi ia dikunci, tiada benang lain boleh mengakses sumber yang dilindungi. Jika tiada kunci, sumber itu berjaya diperolehi, jika tidak, ia menyekat dan menunggu sumber itu tersedia. Dalam satu perkataan, vlaue mutex benang boleh menjadi negatif.
Berbilang benang
Benang ialah unit terkecil yang berjalan secara bebas dalam komputer dan menggunakan sumber sistem yang sangat sedikit semasa berjalan. Berbanding dengan berbilang proses, berbilang proses mempunyai beberapa kelebihan yang tidak dimiliki oleh pelbagai proses Perkara yang paling penting ialah: untuk multi-benang, ia boleh menjimatkan sumber lebih daripada berbilang proses.
Penciptaan Benang
Di Linux, benang yang baru dibuat bukan dalam proses asal, tetapi sistem memanggil klon() melalui panggilan sistem. Sistem menyalin proses yang betul-betul sama dengan proses asal dan melaksanakan fungsi benang dalam proses ini.
Di Linux, penciptaan benang dicapai melalui fungsi pthread_create():
pthread_create()
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*st
Antaranya:
benang mewakili penunjuk jenis pthread_t;
attr digunakan untuk menentukan beberapa sifat utas;
start_routine mewakili penunjuk fungsi, iaitu fungsi panggilan benang;
arg mewakili parameter yang dihantar ke fungsi panggilan benang.
Apabila benang berjaya dicipta, fungsi pthread_create() mengembalikan 0. Jika nilai pulangan bukan 0, ini bermakna penciptaan benang gagal. Untuk atribut benang, ia ditakrifkan dalam struktur pthread_attr_t.
Proses penciptaan benang adalah seperti berikut:
#include #include #include #include void* thread(void *id){ pthread_t newthid; newthid = pthread_self(); printf("this is a new thread, thread ID is %u\n", newthid); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); printf("main thread, ID is %u\n", pthread_self()); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, NULL) != 0){ printf("thread create failed!\n"); return 1; } } sleep(2); free(pt); return 0; }
Dalam kod di atas, fungsi pthread_self() digunakan Fungsi fungsi ini adalah untuk mendapatkan ID benang ini. sleep() dalam fungsi utama digunakan untuk meletakkan proses utama dalam keadaan menunggu untuk membolehkan pelaksanaan thread selesai. Kesan pelaksanaan akhir adalah seperti berikut:
Jadi, bagaimana untuk menggunakan arg untuk menghantar parameter kepada sub-benang Pelaksanaan khusus adalah seperti berikut:
#include #include #include #include void* thread(void *id){ pthread_t newthid; newthid = pthread_self(); int num = *(int *)id; printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); return NULL; } int main(){ //pthread_t thid; int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); printf("main thread, ID is %u\n", pthread_self()); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } sleep(2); free(pt); free(id); return 0; }
Kesan pelaksanaan akhir ditunjukkan dalam rajah di bawah:
Apa yang akan berlaku jika proses utama tamat awal seperti yang ditunjukkan dalam kod berikut:
#include #include #include #include void* thread(void *id){ pthread_t newthid; newthid = pthread_self(); int num = *(int *)id; printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); sleep(2); printf("thread %u is done!\n", newthid); return NULL; } int main(){ //pthread_t thid; int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); printf("main thread, ID is %u\n", pthread_self()); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } //sleep(2); free(pt); free(id); return 0; }
Pada masa ini, proses utama berakhir lebih awal, dan proses akan mengitar semula sumber Pada masa ini, utas akan keluar dari pelaksanaan, dan hasil yang dijalankan adalah seperti berikut:
.Benang tergantung
Dalam proses pelaksanaan di atas, untuk membolehkan utas utama menunggu setiap sub-utas menyelesaikan pelaksanaan sebelum keluar, fungsi free() digunakan Dalam Linux multi-threading, fungsi pthread_join() juga boleh digunakan untuk menunggu benang lain , bentuk khusus fungsinya ialah:
int pthread_join(pthread_t thread, void **retval);
Fungsi pthread_join() digunakan untuk menunggu penghujung benang dan panggilannya akan digantung.
一个线程仅允许一个线程使用pthread_join()等待它的终止。
如需要在主线程中等待每一个子线程的结束,如下述代码所示:
#include #include #include #include void* thread(void *id){ pthread_t newthid; newthid = pthread_self(); int num = *(int *)id; printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); printf("thread %u is done\n", newthid); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); printf("main thread, ID is %u\n", pthread_self()); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
最终的执行效果如下所示:
注:在编译的时候需要链接libpthread.a:
g++ xx.c -lpthread -o xx
互斥锁mutex
多线程的问题引入
多线程的最大的特点是资源的共享,但是,当多个线程同时去操作(同时去改变)一个临界资源时,会破坏临界资源。如利用多线程同时写一个文件:
#include #include const char filename[] = "hello"; void* thread(void *id){ int num = *(int *)id; // 写文件的操作 FILE *fp = fopen(filename, "a+"); int start = *((int *)id); int end = start + 1; setbuf(fp, NULL);// 设置缓冲区的大小 fprintf(stdout, "%d\n", start); for (int i = (start * 10); i "%d\t", i); } fprintf(fp, "\n"); fclose(fp); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
执行以上的代码,我们会发现,得到的结果是混乱的,出现上述的最主要的原因是,我们在编写多线程代码的过程中,每一个线程都尝试去写同一个文件,这样便出现了上述的问题,这便是共享资源的同步问题,在Linux编程中,线程同步的处理方法包括:信号量,互斥锁和条件变量。
互斥锁
互斥锁是通过锁的机制来实现线程间的同步问题。互斥锁的基本流程为:
初始化一个互斥锁:pthread_mutex_init()函数
加锁:pthread_mutex_lock()函数或者pthread_mutex_trylock()函数
对共享资源的操作
解锁:pthread_mutex_unlock()函数
注销互斥锁:pthread_mutex_destory()函数
其中,在加锁过程中,pthread_mutex_lock()函数和pthread_mutex_trylock()函数的过程略有不同:
当使用pthread_mutex_lock()函数进行加锁时,若此时已经被锁,则尝试加锁的线程会被阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock()函数有返回值时,说明加锁成功;
而使用pthread_mutex_trylock()函数进行加锁时,若此时已经被锁,则会返回EBUSY的错误码。
同时,解锁的过程中,也需要满足两个条件:
解锁前,互斥锁必须处于锁定状态;
必须由加锁的线程进行解锁。
当互斥锁使用完成后,必须进行清除。
有了以上的准备,我们重新实现上述的多线程写操作,其实现代码如下所示:
#include #include pthread_mutex_t mutex; const char filename[] = "hello"; void* thread(void *id){ int num = *(int *)id; // 加锁 if (pthread_mutex_lock(&mutex) != 0){ fprintf(stdout, "lock error!\n"); } // 写文件的操作 FILE *fp = fopen(filename, "a+"); int start = *((int *)id); int end = start + 1; setbuf(fp, NULL);// 设置缓冲区的大小 fprintf(stdout, "%d\n", start); for (int i = (start * 10); i "%d\t", i); } fprintf(fp, "\n"); fclose(fp); // 解锁 pthread_mutex_unlock(&mutex); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); // 初始化互斥锁 if (pthread_mutex_init(&mutex, NULL) != 0){ // 互斥锁初始化失败 free(pt); free(id); return 1; } for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
最终的结果为:
通过本文的介绍,您应该已经了解了Linux多线程编程中的常见锁类型、正确使用锁的方法以及如何避免竞争和死锁等问题。锁机制是多线程编程中必不可少的一部分,掌握它们可以使您的代码更加健壮和可靠。在实际应用中,应该根据具体情况选择合适的锁类型,并遵循最佳实践,以确保程序的高性能和可靠性。
Atas ialah kandungan terperinci Penjelasan terperinci tentang kunci pengaturcaraan berbilang benang Linux: bagaimana untuk mengelakkan pertengkaran dan kebuntuan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!