Linux 线程互斥

                    Linux 线程互斥_第1张图片

在这漫长的暑假,学习让我感到不再孤单,一起为了秋招努力吧!(有一位一起学习的朋友,最近拿到了腾讯的实习,好羡慕~)

目录

进程线程间的互斥相关背景概念

多执行流下没有互斥锁带来的问题 

线程不安全的原因

互斥量接口

pthread_mutex_init

pthread_mutex_destroy

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

pthread_mutex_lock

pthread_mutex_unlock

互斥锁&抢票逻辑代码

C++11里面的互斥锁

锁的原理 

汇编代码层次理解 

补充

静态的互斥锁使用


进程线程间的互斥相关背景概念

临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

是不是感觉太官方了,我们来看一下稍微通俗的解释(方便将来面试来讲):

Linux 线程互斥_第2张图片

多执行流下没有互斥锁带来的问题 

我们先不使用互斥锁,实现一个抢票逻辑看会出现什么问题。

代码测试:

test.cc

#include 
#include 
#include 

using namespace std;

int tickes = 1000;
void* ThreadRun(void* args)
{
    int id = *(int*)args;
    delete (int*)args;

    while(true)
    {
        if(tickes > 0)
        {
            //抢票
            usleep(1000);
            cout << "我是[" << id <<"] 我要抢的票是: " << tickes << endl;
            tickes--;
        }
        else
        {
            break;
        }
    }
}

int main()
{
    pthread_t tid[5];
    for(int i = 0; i < 5; i++)
    {
        int* id = new int(i);
        pthread_create(tid+i, nullptr, ThreadRun, (void*)id);
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

Makefile

test:test.cc
	g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
	rm -f test

运行结果:

Linux 线程互斥_第3张图片

这时候我们会发现,多个线程在抢票过程中,会抢到负数的情况,这就意味着票卖多了。这种情况是商家不愿意看到的。所以这段代码在多执行流下存在线程安全问题! 

线程不安全的原因

Linux 线程互斥_第4张图片

实际上ticket--并非原子性的,它实际上要进行计算是要经过多步的,在这期间就有可能被多个线程重入,导致可能出现问题。 

如何解决上面的问题呢?对临界区进行加锁!

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

2、如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3、如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁。

Linux上提供的这把锁叫互斥量。

互斥量接口

pthread_mutex_init

Linux 线程互斥_第5张图片

pthread_mutex_t* restrict mutex:传入要初始化锁的地址 

const pthread_mutexattr_t* restrict attr:我们不用关心,设置成nullptr

pthread_mutex_destroy

Linux 线程互斥_第6张图片

pthread_mutex_t* mutex:传入要释放锁的地址 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

Linux 线程互斥_第7张图片

定义在全局的或静态的锁,不用再手动初始化和销毁,直接用就可以了。 

pthread_mutex_lock

Linux 线程互斥_第8张图片

 pthread_mutex_t* mutex:传入锁的地址进行上锁

pthread_mutex_unlock

Linux 线程互斥_第9张图片

pthread_mutex_t:传入锁的地址,给对应的锁解开 

互斥锁&抢票逻辑代码

test.cc

#include 
#include 
#include 

using namespace std;

class Ticket
{
public:
    Ticket()
        :_tickes(1000)
    {
        pthread_mutex_init(&_mtx, nullptr);
    }
    ~Ticket()
    {
        pthread_mutex_destroy(&_mtx);
    }
    bool GetTicket()
    {
        bool rets = true;//rets是每个线程私有的

        //对临界区进行加锁保护
        //执行这部分代码的执行流是互斥的,也是串行的
        pthread_mutex_lock(&_mtx);
        if(_tickes > 0)
        {
            //抢票
            usleep(1000);
            cout << "我是[" << pthread_self() << "] 我要抢的票是: " << _tickes << endl;
            _tickes--;
        }
        else
        {
            //没有票
            printf("票已经抢空了\n");
            rets = false;
        }
        pthread_mutex_unlock(&_mtx);

        return rets;
    }
private:
    int _tickes;
    pthread_mutex_t _mtx;
};

void* ThreadRun(void* args)
{
    Ticket* t = (Ticket*)args;

    while(true)
    {
        if(!t->GetTicket())
        {
            break;
        }
    }
}

int main()
{
    Ticket* t = new Ticket;
    
    pthread_t tid[5];
    for(int i = 0; i < 5; i++)
    {
        int* id = new int(i);
        pthread_create(tid+i, nullptr, ThreadRun, (void*)t);
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

运行结果(部分):

Linux 线程互斥_第10张图片

 这时候我们发现不会出现负数的情况了。

C++11里面的互斥锁

Linux 线程互斥_第11张图片

运行结果:

Linux 线程互斥_第12张图片 

锁的原理 

Linux 线程互斥_第13张图片

汇编代码层次理解 

Linux 线程互斥_第14张图片

原子性:一行代码经过编译后,只有一行汇编代码。ticket--经过汇编是有多行代码的,但是互斥量在设计的时候,绝对不是 "--" 设计的,而是使用上面的方法完成原子性的逻辑。

Linux 线程互斥_第15张图片 

       实际上只有互斥还是存在一定的问题,比如一个线程申请锁处理完逻辑释放锁之后,它会再次竞争锁,甚至在其他线程还没唤醒之前又进行申请锁释放锁的逻辑。因为其它在挂起等待的线程被唤醒是有一定代价的,竞争力明显小于刚释放锁的线程,除非它的时间片到了或收到信号,这时候被挂起的线程才有可能竞争到锁。

补充

Linux 线程互斥_第16张图片

实际上临界区我们加完锁之后,就不用担心重入的问题了。

静态的互斥锁使用

test.cc

pthread_mutex_t mymtx = PTHREAD_MUTEX_INITIALIZER;
class Ticket
{
public:
    bool GetTicket()
    {
        bool rets = true;//rets是每个线程私有的

        //对临界区进行加锁保护
        //执行这部分代码的执行流是互斥的,也是串行的
        pthread_mutex_lock(&mymtx);
        if(_tickes > 0)
        {
            //抢票
            usleep(1000);
            cout << "我是[" << pthread_self() << "] 我要抢的票是: " << _tickes << endl;
            _tickes--;
        }
        else
        {
            //没有票
            printf("票已经抢空了\n");
            rets = false;
        }
        pthread_mutex_unlock(&mymtx);
        
        return rets;
    }
private:
    int _tickes = 1000;
    pthread_mutex_t _mtx;
};

void* ThreadRun(void* args)
{
    Ticket* t = (Ticket*)args;

    while(true)
    {
        if(!t->GetTicket())
        {
            break;
        }
    }
}

int main()
{
    Ticket* t = new Ticket;

    pthread_t tid[5];
    for(int i = 0; i < 5; i++)
    {
        int* id = new int(i);
        pthread_create(tid+i, nullptr, ThreadRun, (void*)t);
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

Linux 线程互斥_第17张图片

运行结果:

Linux 线程互斥_第18张图片 

 看到这里,给博主点个赞吧~

                  Linux 线程互斥_第19张图片

你可能感兴趣的:(Linux,博客,linux,运维,服务器,后端)