最近在写多进程和Linux中的各种锁的文章,总觉得只有文字讲解虽然能够知道多进程和互斥锁是什么,但是还是不知道到底该怎么用,今天我就用一个买票的例子来给大家讲解一下多线程和互斥锁到底该如何使用,相信大家对于这个知识点会有一个更深的理解。
二、场景介绍今天以实际场景来给大家介绍一下多线程和互斥锁,场景的描述如下:
- 有一个全局变量num
- 线程1和线程2都可以对num做加一操作
那理想情况下num的变化应该是这样的:
num=1 num=2 num=3 num=4 num=5 num=6 num=7 num=8 num=9 num=10 num=11 num=12 num=13 num=14 num=15 num=16 num=17 num=18 num=19 num=20 num=21 num=22 num=23 num=24 num=25 num=26 num=27 num=28 num=29 num=30 num=31 num=32 num=33 num=34 num=35 num=36 num=37 num=38 num=39 num=40 num=41
这是我们的理想状态,但是如果用多线程会出现什么现象呢?我们一起来通过编程实现一下多线程。
三、编程测试我们通过上面的描述大家应该已经理解了这个场景是在干什么事了,那么如何使用多线程让程序同时对num进行操作呢?
如果要在C语言中使用多线程,我们需要用到线程创建函数pthread_create和线程回收函数pthread_join。具体这两个函数的使用大家可以通过man+函数名的方式在控制台进行查看。
知道了如何使用这两个函数,下面我们就写一个多线程的例子:
main.c
#include <stdio.h> #include <pthread.h> int num=0; //需要开启多进程的函数 void * fun_1(void * args) { while(num<40) { num++; printf("num=%d\n",num); } return NULL; } //需要开启多进程的函数 void * fun_2(void * args) { while(num<40) { num++; printf("num=%d\n",num); } return NULL; } int main() { pthread_t id[2]; //创建线程 pthread_create(&id[0],NULL,fun_1,NULL); pthread_create(&id[1],NULL,fun_2,NULL); //等待线程的结束 pthread_join(id[0],NULL); pthread_join(id[1],NULL); }
通过gcc工具来编译这个代码,具体命令如下:
gcc main.c -pthread -o main
编译完后会出现一个可执行文件main,执行该可执行文件,结果如下:
我们可以看到其实代码并不是像我们想想的那样两个进程进行依次加一,这样明显是不对的,那怎么保证只有线程一完成了加一线程二才能对num的字进行操作呢?这里就不得不提C语言中的锁了,今天就以互斥锁来解决这个问题。
四、互斥锁介绍互斥锁是一种用于确保同一时间只有一个线程访问共享资源的机制。它是一种控制多个线程对共享资源的访问,以确保线程安全。互斥锁可以防止共享资源的竞争条件,从而保证程序的正确性。
互斥锁也可以用来实现延迟,这是一种确保多个线程在某个特定时刻只有一个线程可以执行特定代码段,直到其他线程完成指定任务的机制。这种机制可以确保在多线程环境中的正确性,以及确保线程之间的数据一致性。
要使用互斥锁,首先需要创建一个互斥锁,通常使用内置的函数或API。然后,在访问共享数据之前,线程需要调用该互斥锁的pthread_mutex_lock()函数,以获取对共享数据的独占访问权。在访问完成后,线程应调用pthread_mutex_unlock()函数释放互斥锁,以便其他线程可以访问共享数据。
此外,在使用互斥锁时,应特别注意死锁的可能性,即两个或多个线程因互相等待对方释放互斥锁而永久阻塞。为了避免死锁,应该确保线程在访问共享数据时,遵循一致的锁定顺序。
下面是我们更改之后的代码,可以解决以上问题:
#include <stdio.h> #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int num=0; //需要开启多进程的函数 void * fun_1(void * args) { while(num<40) { sleep(2); pthread_mutex_lock(&mutex);//加锁 printf("fun_1:pthread_mutex_lock\n"); num++; printf("num=%d\n",num); pthread_mutex_unlock(&mutex);//解锁 printf("fun_1:pthread_mutex_unlock\n"); } return NULL; } //需要开启多进程的函数 void * fun_2(void * args) { while(num<40) { sleep(2); pthread_mutex_lock(&mutex);//加锁 printf("fun_2:pthread_mutex_lock\n"); num++; printf("num=%d\n",num); pthread_mutex_unlock(&mutex);//解锁 printf("fun_2:pthread_mutex_unlock\n"); } return NULL; } int main() { pthread_t id[2]; //创建线程 pthread_create(&id[0],NULL,fun_1,NULL); pthread_create(&id[1],NULL,fun_2,NULL); //等待线程的结束 pthread_join(id[0],NULL); pthread_join(id[1],NULL); }
通过代码的打印我们可以看出,只有在加锁的线程释放了锁,其他的进程才能对资源进行操作,这就是锁的作用。
这里可以试一下如果让线程二加完锁之后不进行释放锁会怎么样呢?我们来看一下:
可以看到如果线程二对资源加了锁,但是不释放锁的话,线程一和线程二都将无法对资源进行操作。