线程同步与互斥

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

共享资源

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

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

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

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

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

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

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

相关推荐
娶不到胡一菲的汪大东1 小时前
C# 泛型 委托 接口
开发语言·windows·c#
Antonio9151 小时前
【Swift】UIKit:UISegmentedControl、UISlider、UIStepper、UITableView和UICollectionView
开发语言·ios·swift
IUGEI2 小时前
【计算机网络】HTTP/3如何实现可靠传输?
java·网络·后端·网络协议·tcp/ip·计算机网络·http
0***142 小时前
JavaScript视频处理案例
开发语言·javascript·音视频
ceclar1232 小时前
C#常用集合的使用
开发语言·windows·c#
z***I3942 小时前
PHP Composer
开发语言·php·composer
u***u6852 小时前
JavaGraphQL案例
java·spring boot·后端
1***81532 小时前
Swift在服务端开发的可能性探索
开发语言·ios·swift
2501_941879812 小时前
Python在微服务高并发异步流量控制与动态限流熔断架构中的实践
java·开发语言