2023/9/04
1.线程同步
线程同步并不是并行执行(不是说一起执行好几个线程呀,那肯定会混乱的),是线性的执行,是有先后顺序进行执行的!效率降低,但是保证了安全。
2.互斥锁
互斥锁是线程同步最常用的方式,通过互斥锁可以锁定一个代码块,被锁定的代码块,所有线程只能顺序执行(不能并行处理),这样可以很好解决共享资源混乱的问题。
2.1互斥锁函数
pthread_mutex_t mutex;
通过以上的命令可以直接创建一把锁,在当前创建锁对象中保存了当前这把锁的状态信息;锁定还是打开,如果是锁定状态还记录了给这把锁加所的线程信息(线程ID)。一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程在对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被接触阻塞,一般情况下,每一个共享资源一个对应一把互斥锁,但是和线程数量是无关的!
2.1.1初始化互斥锁
//初始化互斥锁
//restrict:是一个关键字,用来修饰指针,只有关键字修饰的指针可以访问指向的内存地址,其他指针是不行的。
int pthread_mutex_init()
/*
第一个参数为指针变量,是之前创建的mutex。此处为&mutex
第二个参数一般为空
*/
/*释放互斥锁资源*/
int pthread_mutex_destory()
/*上面两个成对使用*/
2.1.2修改互斥锁状态
/*第一个加锁,第二个解锁*/
/*加锁解锁之间的代码叫临界区*/
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
/*线程单独执行锁定区域的函数,解锁后其他线程才能进来*/
2.1.3尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex)
/*
第一种情况:如果这把锁没有被锁定,线程加锁成功
第二种情况:如果锁变量被锁住了,调用这个函数加锁的线程不会阻塞,加锁失败就会返回错误信号
*/
不是所有线程都可以对互斥锁解锁,哪个线程加的锁,哪个线程可以解锁成功。并且在做线程同步期间,这个锁的资源是不能被释放的!所以要注意临时变量等,千万不要突然消失了。不然锁直接就没了。
3.互斥锁的使用
这是一个数数(shu第三声,shu第四声)的双线程!
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAX 50
pthread_mutex_t mutex;
int number;
void* funcA_num(void* arg)
{
for(int i = 0; i < MAX; i++)
{
pthread_mutex_lock(&mutex);
int cur = number;
cur++;
usleep(10);
number = cur;
printf("Thread A, id = %lu, number =%d \n", pthread_self(), number);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void* funcB_num(void* arg)
{
for(int i = 0; i < MAX; i++)
{
pthread_mutex_lock(&mutex);
int cur = number;
cur++;
number = cur;
printf("Thread B, id = %lu, number =%d \n", pthread_self(), number);
pthread_mutex_unlock(&mutex);
usleep(5);/*这个没必要加进来*/
}
return NULL;
}
int main()
{
pthread_t threadA, threadB;
pthread_mutex_init(&mutex, NULL); /*初始化线程锁*/
/*创建两个子线程*/
pthread_create(&threadA, NULL, funcA_num, NULL);
pthread_create(&threadB, NULL, funcB_num, NULL);
/*阻塞资源回收*/
pthread_join(threadA, NULL);
pthread_join(threadB, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
我们发现最终也不是按照A,B这个顺序进行执行的,那是由于之前说过的,两个线程抢时间片完全就是一个随机的情况。
4.死锁
当多个线程访问共享资源,需要加锁,如果锁没用好,会造成死锁,所有线程都被阻塞了。两次锁就是死锁。必须解开才能再加锁。没解开就调用也是死锁。
1.加锁后忘记解锁
void func()
{
for(int i = 0; i <6; ++i)
{
/*当前线程A加锁成功,当前循环完毕没有解锁。在下一轮循环的时候自己被阻塞了*/
/*其余的线程也被阻塞了*/
pthread_mutex_lock(&mutex);
/*忘记解锁*/
}
}
2.如果有条件判定,并且直接return的话,解锁这个代码没被执行。
void func()
{
for(int i = 0; i <6; ++i)
{
/*当前线程A加锁成功,当前循环完毕没有解锁。在下一轮循环的时候自己被阻塞了*/
/*其余的线程也被阻塞了*/
pthread_mutex_lock(&mutex);
/*忘记解锁*/
if(xxx)
{
return;
}
pthread_mutex_unlock(&mutex);
}
}
3.两次加锁(隐藏版)
主要此时B里调用了A的函数,B加锁了一次。A里面又加锁了一次,变成了死锁。
void funcA()
{
for(int i = 0; i < 6; ++i)
{
pthread_mutex_lock(&mutex);
………………
………………
pthread_mutex_unlock(&mutex);
}
}
void funcB()
{
for(int i = 0; i < 6; ++i)
{
pthread_mutex_lock(&mutex);
funcA();
………………
………………
pthread_mutex_unlock(&mutex);
}
}
解决方法:
- 避免多次锁定,多检查(vscode可以直接检查死锁)。
- 对共享资源访问完毕,一定要解锁。
- 如果程序中有多把锁,可以控制对锁的访问顺序,顺序访问共享资源(但很难做到),另外在对其他互斥锁做加锁操作之前,先释放当前线程拥有的互斥锁。