当前位置: 首页 > news >正文

线程(二) linux 互斥

目录

概念补充

互斥量mutex

为什么有互斥

例:多线程抢票

--操作

互斥量(锁)

操作

例:多线程抢票

补充

互斥量的封装

小知识


概念补充

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

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


互斥:任何时刻,互斥保证只有一个执行流进入临界区,访问临界资源,对临界资源起保护作用

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

互斥量mutex

为什么有互斥

多线程如果并发(同一时间段切换着访问同一资源)访问了共享的资源,共享资源的安全性无法保证.

比如

1.多个线程同时访问显示器文件,向屏幕中打印数据,会因为线程调度切换导致打印错乱的问题;

2.如果有全局变量,多个线程都访问会出问题

例:多线程抢票
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>int ticket = 100;void *route(void *arg)
{char *id = (char*)arg;while ( 1 ) {if ( ticket > 0 ) {usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;} else {break;}}
}
int main(  )
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);
}

执行到最后,票数会变为负数,为什么呢?

因为在汇编层面,代码会变成多条汇编语句,而单条汇编语句的执行是不会被打断的,执行了就一定会执行完,是原子的; 

当执行到ticket为1时,某线程可能刚执行完ticket>0的汇编语句,线程就因为时间片耗尽等原因被切换了,其他线程这时执行ticket>0的汇编语句取到的也是1,然后再次切换,所有线程都在ticket>0这里判断成功了,之后向下执行,导致ticket最后减到了-3.

这就是多线程访问公共资源导致的问题之一.

--操作

--被汇编为多条指令,可能一个线程刚从内存取数据,时间片到了,调度另一个线程,另一个线程取数据,分析数据,执行--,然后结束,调度回之前的线程,结果两个线程执行完,结果只--了一次.

所以--和++操作多线程下也是不安全的.

互斥量(锁)

要解决上面的问题,需要做到三点

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

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

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


linux提供了一把锁,叫互斥量

操作

全局互斥量

直接使用宏来初始化一个全局mutex锁

局部互斥量

局部互斥量需要初始化和销毁

申请锁和释放锁

int pthread_mutex_lock(pthread_mutex_t *mutex);//申请

int pthread_mutex_unlock(pthread_mutex_t *mutex);//释放

使用时,将临界区代码放在申请和释放锁的内部.

例:多线程抢票

#include <iostream>

#include <unistd.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

int tickets = 100;

void *func(void *args)

{

    (void*)args;

    while(true)

    {

        {

            pthread_mutex_lock(&lock);

            if(tickets>0)

            {

                sleep(0.1);

                printf("剩余票数: %d\n",--tickets);

            }

            else

            {

                break;

            }

            pthread_mutex_unlock(&lock);

        }//加一层花括号表示锁住的区域

       

    }

    return (void*)0;

}


 

int main()

{

    pthread_t t1,t2,t3;

    pthread_create(&t1,nullptr,func,nullptr);

    pthread_create(&t2,nullptr,func,nullptr);

    pthread_create(&t3,nullptr,func,nullptr);

    pthread_join(t1,nullptr);

    pthread_join(t2,nullptr);

    pthread_join(t3,nullptr);

    return 0;

}

补充

1.互斥量会被多个线程并发访问,所以它也是临界资源,怎么保证自身的安全?

解: 锁是原子的

实现原子性的方案:

(1)硬件上:关闭所有中断

(2)软件上:因为每一条汇编语句都是原子的,所以只通过一条汇编语句实现互斥量(锁)即可

linux中使用交换的机制,大体流程:

在内存中会有mutex变量的数据区域,存放1,cpu中有寄存器存放0

程序执行到锁这里时,直接交换内存和cpu寄存器中的值,然后判断寄存器中的值,是1,继续执行,是0,该线程不向下执行,而是或者阻塞等待,或者不断重复申请,这样只有申请到1的线程可以执行临界区代码,其他线程只能等.

解锁就是将1设置到内存中的mutex区域,以便其他线程申请

2.申请成功,线程继续运行,申请失败,线程会被阻塞

3.如果进入了临界区,线程还会被切换导致并发问题吗?

会被切换,但即使被切换,此进程未解锁时,其他线程进行申请锁也会失败,阻塞住

互斥量的封装

<Lock.hpp>

#pragma once

#include <iostream>

#include <unistd.h>

#include <pthread.h>

class Mutex

{

    pthread_mutex_t _mutex;

public:

    Mutex(const Mutex&) = delete;

    const Mutex& operator = (const Mutex&) = delete;

    Mutex()

    {

        int n = pthread_mutex_init(&_mutex,nullptr);

        (void)n;

    }

    void Lock()

    {

        int n = pthread_mutex_lock(&_mutex);

        (void)n;

    }

    void Unlock()

    {

        int n = pthread_mutex_unlock(&_mutex);

        (void)n;

    }

    pthread_mutex_t *Get()

    {

        return &_mutex;

    }

    ~Mutex()

    {

        int n = pthread_mutex_destroy(&_mutex);

        (void)n;

    }

};

class LockGuard

{

    Mutex &_mutex;

public:    

    LockGuard(Mutex& mutex):_mutex(mutex)

    {

        _mutex.Lock();

    }

    ~LockGuard()

    {

        _mutex.Unlock();

    }

};

LockGarud用来管理锁

所以多线程抢票的代码可以改为

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "Lock.hpp"int ticket = 1000;Mutex mutex;
void *route(void *arg)
{char *id = (char *)arg;while (1){LockGuard lockguard(mutex); // 使⽤RAII⻛格的锁 if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}
int main(void)
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void*)"thread 1");pthread_create(&t2, NULL, route, (void*)"thread 2");pthread_create(&t3, NULL, route, (void*)"thread 3");pthread_create(&t4, NULL, route, (void*)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);
}

小知识

1.锁的使用的最佳实践: 加锁和解锁时,囊括的区域尽量是最小集

2.互斥的负面影响:降低程序的运行效率


文章转载自:
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://www.dtcms.com/a/280685.html

相关文章:

  • JVM——有哪些常见的垃圾收集器
  • Props
  • 时序数据库与AI的融合:智能时代的数据基石
  • 027_国际化与本地化
  • Spring应用抛出NoHandlerFoundException、全局异常处理、日志级别
  • FreeRTOS学习笔记——移植说明、任务创建
  • 【Ubuntu22.04】repo安装方法
  • Linux715 磁盘管理:逻辑卷
  • 聊聊MySQL中的buffer pool
  • Spring Boot目录变文件夹?3步解决!
  • Unity Editor下拉框,支持搜索,多层级
  • BGP服务器和多线服务器的不同之处
  • Python初学者笔记第十三期 -- (常用内置函数)
  • 原点安全签约金网络数科,共建一体化数据安全防护体系
  • Docker 镜像(Image)常用命令总结
  • ASP .NET Core 8结合JWT轻松实现身份验证和授权
  • CMake基础:覆盖项目开发的五大配套工具
  • LLM面试题及讲解 4
  • VSCode同时支持Vue2和Vue3开发的插件指南
  • 【编程】-环形缓冲区
  • 安全参綉25暑假第一次作业
  • 超详细 anji-captcha滑块验证uniapp微信小程序前端组件
  • 备忘录设计模式
  • asyncio 与 uvloop
  • 策略设计模式分析
  • 如何将华为文件传输到电脑
  • Linux的用户和用户组与权限解析、环境变量说明与配置、sudo配置解析和使用
  • HarmonyOS从入门到精通:自定义组件开发指南(七):自定义事件与回调
  • 涨停板池,跌停板池,炸板池,次新股池,强势股池数据接口
  • 单臂路由实现VLAN互通实验