我们为什么要使用互斥锁呢?首先,我们需要了解临界资源(也就是共享资源)的概念。
临界资源:
临界资源通常需要被保护,以确保在任何给定时间只有一个线程或进程能够访问它,这就是所谓的临界区。在临界区内,对临界资源的访问需要通过同步机制来控制,比如使用互斥锁、信号量、条件变量等。通过这些同步机制,可以确保对临界资源的访问是原子的,不会出现竞态条件或数据竞争等问题,从而保证程序的正确性和稳定性。
比如写文件,只能由一个线程写,假若多个线程同时对文件操作那肯定会乱。也可以看这个up讲的线程锁-互斥锁这个例子,你就会更进一步明白为什么要使用互斥锁。
那谈到互斥锁,如何来创建呢?有静态方式和动态方式两种,分别称为静态锁和动态锁。
静态锁
初始化方式
静态锁是在编译时刻进行初始化的,通过宏或特殊的语法来完成。在 POSIX 线程库中,可以使用 PTHREAD_MUTEX_INITIALIZER 宏来静态初始化互斥锁。
生命周期
静态锁的生命周期与程序的生命周期相同,它在程序启动时被初始化,在程序结束时被销毁(也就说不用写销毁代码进行销毁,这点不同于动态锁,后面的代码展示到了这点)。因此,静态锁在整个程序的执行过程中都是可用的。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
这行代码声明了一个名为 mutex 的互斥锁,并使用了 PTHREAD_MUTEX_INITIALIZER 进行了初始化。
适合使用静态锁的情况:
- 互斥锁对象的数量是固定的
如果程序中的互斥锁数量是固定的,并且在编译时就能够确定,那么静态锁会更方便,因为它们在编译时就被初始化了。 - 需要简化代码
静态锁的初始化是通过宏或特殊的语法完成的,相对于动态锁,使用静态锁可能能够简化代码。
静态锁使用例程
#include <stdio.h>
#include <unistd.h>
#include <string.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
FILE *fp;
void *func2(void *arg){
pthread_detach(pthread_self()); //分离线程,自动回收。线程分离三种方式中的其中一种
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
usleep(1);
i++;
}
pthread_mutex_unlock(&mutex);
i=0;
usleep(1);
}
pthread_exit("func2 exit");
}
void *func(void *arg){
pthread_detach(pthread_self());
printf("This is func1 thread\n");
char str[]="You read func1 thread\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
i++;
usleep(1);
}
pthread_mutex_unlock(&mutex);
i=0;
usleep(1);
}
pthread_exit("func1 exit");
}
int main(){
pthread_t tid,tid2;
void *retv;
int i;
fp = fopen("1.txt","a+");
if(fp==NULL){
perror("fopen");
return 0;
}
pthread_create(&tid,NULL,func,NULL);
pthread_create(&tid2,NULL,func2,NULL);
while(1){
sleep(1);
}
}
对上述代码稍作解释:
pthread_mutex_lock(&mutex)在两个函数中都用到了,我是这么理解的pthread_mutex_lock(&mutex)在哪个函数中使用就保护哪个函数的临界区。换句话说,只有当一个线程成功地获得了互斥锁 mutex,它才能执行临界区内的代码。其他线程如果尝试获得 mutex 的锁,但该锁已被另一个线程持有,则它们将被阻塞,直到该锁被释放。因此,可以在不同的函数中分别使用 pthread_mutex_lock(&mutex) 来保护这些函数各自的临界区。这样可以确保同一时间只有一个线程能够访问每个函数的临界区,从而实现线程安全。
需要注意的是,pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)必须成对使用。
动态锁
初始化方式
动态锁是在运行时刻进行初始化的,需要调用专门的初始化函数(比如 pthread_mutex_init)来创建和初始化锁。
生命周期
动态锁的生命周期可以通过初始化和销毁函数来控制,它们在需要时创建,在不再需要时销毁。这种灵活性使得动态锁更适合于某些特定场景,比如动态创建的线程或者需要动态管理资源的情况。
适合使用动态锁的情况:
-
需要动态创建锁:
如果程序需要在运行时动态地创建锁,并且锁的数量或属性可能会根据程序运行时的条件而变化,那么动态锁会更适合。 -
需要更灵活的管理资源:
动态锁的生命周期可以根据需要进行控制,这使得程序能够更灵活地管理资源,避免资源的浪费或者不足。
动态锁使用例程
#include <pthread.h>
#include <stdio.h>
void *client_handler(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
pthread_mutex_lock(mutex);
// Critical section
printf("Processing request...\n");
pthread_mutex_unlock(mutex);
return NULL;
}
int main() {
int num_threads = 10; // Number of threads can vary
pthread_t threads[num_threads];
pthread_mutex_t mutex;
// Initialize dynamic mutex
pthread_mutex_init(&mutex, NULL);
// Start client threads
for (int i = 0; i < num_threads; i++) {
pthread_create(&threads[i], NULL, client_handler, (void *)&mutex);
}
// Join threads
for (int i = 0; i < num_threads; i++) {
pthread_join(threads[i], NULL);
}
// Destroy mutex after use
pthread_mutex_destroy(&mutex);
return 0;
}
这是一个简单的动态锁使用。
在使用过程中,最好只使用一把锁,从而避免死锁。
静态锁和动态锁的概念比较基础和简单。该篇是目前对学到的东西做的笔记,是个人的一些心得体会。参考了GPT及一些网络资源,后面还有学习心得会继续补充。新手,如有建议或不对的,欢迎讨论一下哦。