线程同步与互斥

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

共享资源

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

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

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

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

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

互斥量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会被带走。

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

相关推荐
Z9fish3 分钟前
sse哈工大C语言编程练习20
c语言·开发语言·算法
CodeCaptain3 分钟前
nacos-2.3.2-OEM与nacos3.1.x的差异分析
java·经验分享·nacos·springcloud
萧鼎27 分钟前
Python 包管理的“超音速”革命:全面上手 uv 工具链
开发语言·python·uv
Anastasiozzzz1 小时前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人1 小时前
通过脚本推送Docker镜像
java·docker·容器
刘琦沛在进步1 小时前
【C / C++】引用和函数重载的介绍
c语言·开发语言·c++
机器视觉的发动机1 小时前
AI算力中心的能耗挑战与未来破局之路
开发语言·人工智能·自动化·视觉检测·机器视觉
铁蛋AI编程实战1 小时前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
HyperAI超神经1 小时前
在线教程|DeepSeek-OCR 2公式/表格解析同步改善,以低视觉token成本实现近4%的性能跃迁
开发语言·人工智能·深度学习·神经网络·机器学习·ocr·创业创新
晚霞的不甘1 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频