线程同步与互斥

一、进程线程间互斥背景相关概念:

共享资源

临界资源:多线程执行流被保护的共享的资源就叫做临界资源。

临界区:每个线程内部,访问临界资源的代码,就叫做临界区。

互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起 保护作用。

多个线程向公共资源写入数据,会发生数据不同步的情况,所以需要进程线程同步。

原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成, 要么未完成。

互斥量mutex:

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量 归属单个线程,其他线程无法获得这种变量。

但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完 成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题:

比如数据不一致问题:

模拟抢票:

cpp 复制代码
int ticket = 100;

void *routine(void *args)
{
    char *id = (char *)args;
    while(1)
    {
        if(ticket>0)
        {
            usleep(1000);//模拟抢票时间
            printf("%s sells ticket:%d\n", id, ticket);//抢到票
            ticket--;//票数--
        }
        else
            break;
    }

    return nullptr;
}

int main()
{
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1, nullptr, routine, (void *)"thread-l");
    pthread_create(&t2, nullptr, routine, (void *)"thread-l");
    pthread_create(&t3, nullptr, routine, (void *)"thread-l");
    pthread_create(&t4, nullptr, routine, (void *)"thread-l");

    pthread_join(t1,nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);
    std::cout << "新线程被分离" << std::endl;

    return 0;
}

票竟然变成负数,不符合实际情况。

ticket载入CPU的寄存器当中,CPU再去读取寄存器做计算。

减完之后的值再写回内存。

执行第二步时,线程切换,线程A保存临时数据,CPU内的数据可以被覆盖了,线程B去做--,减到一半又切换,变成A的线程,线程A的值写入内存。

全局资源没有保护可能会发生并发问题。

二、解决这个问题(锁)

1.代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。

2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许⼀个线程 进入该临界区。

3.如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

互斥锁:

竞争申请锁,线程就要先看到锁,成功继续向后运行,访问临界资源,失败,阻塞挂起申请执行流。

锁提供的能力:执行临界区的代码由并行转为串行。

cpp 复制代码
int ticket = 100;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 对锁进行初始化

void *routine(void *args)
{
    char *id = (char *)args;
    while (1)
    {
        pthread_mutex_lock(&lock); // 加锁
        if (ticket > 0)
        {
            usleep(1000);                               // 模拟抢票时间
            printf("%s sells ticket:%d\n", id, ticket); // 抢到票
            ticket--;                                   // 票数--
            pthread_mutex_unlock(&lock);                // 解锁
            // if里面就是临界区
        }
        else
            break;
    }

    return nullptr;
}

int main()
{
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, routine, (void *)"thread-l");
    pthread_create(&t2, nullptr, routine, (void *)"thread-l");
    pthread_create(&t3, nullptr, routine, (void *)"thread-l");
    pthread_create(&t4, nullptr, routine, (void *)"thread-l");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);
    return 0;
}

三、理解锁:

对临界资源保护,本质是用锁对临界区的代码进行保护

如果有线程不遵守?

加锁之后是否允许线程切换?允许。但是持有锁被切换,其他线程得等该线程执行完代码,释放锁,其他线程才能展开对锁的竞争,执行期间不会被打扰。

寄存器硬件只有一套,但数据可以有多份,当前执行流的上下文,把一个变量的内容交换到CPU寄存器内部本质是把数据获取到当前执行流的上下文。

谁有mutex的1谁就持有锁,申请锁,寄存器al先置0,然后mutex和al的值交换,al的值是1,就申请锁成功,如果线程切换,1会被带走。

线程同步:解锁后不能立即申请第二次锁,外面的线程进行排队,退出的线程必须跑到队尾去申请锁。在保证自习室安全的前提下,让所有的执行流访问临界资源按照一定的顺序进行访问。这是线程同步。

相关推荐
nanxun88620 小时前
记一次诡异的 Docker 容器"串包"故障排查
java
用户1563068103511 天前
Day01 | Java 基础(Java SE)
java
行者全栈架构师1 天前
Maven dependency:tree 的 8 个高级用法
java·后端
行者全栈架构师1 天前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_01 天前
mac(m5)平台编译openjdk
java
唐青枫2 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马2 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户3721574261352 天前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户3721574261352 天前
Java 打印 Word 文档:从基础打印到高级设置
java
用户3521802454753 天前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程