第二集 C/C++多线程编程(奇迹行者还在更新)

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);
    }
}

解决方法:

  1. 避免多次锁定,多检查(vscode可以直接检查死锁)。
  2. 对共享资源访问完毕,一定要解锁。
  3. 如果程序中有多把锁,可以控制对锁的访问顺序,顺序访问共享资源(但很难做到),另外在对其他互斥锁做加锁操作之前,先释放当前线程拥有的互斥锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值