Linux线程互斥

Linux线程互斥

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

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

互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

例如:

//操作共享变量会有问题的售票系统代码
#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(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);
}

该程序运行可能会产生如下结果:

在这里插入图片描述

造成此中情况的原因

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • ticket-- 操作本身就不是一个原子操作
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

要想解决多线程的数据不一致问题,就需要做到以下几点:

  • 代码必须要有互斥行为,当一个线程进入临界区执行代码时,不允许其他线程进入该临界区。
  • 如果有多个线程同时请求执行临界区代码,并且临界区没有线程在执行代码,那么只允许一个线程进入该临界区。
  • 如果线程不在临界区中执行代码,那么该线程不能阻止其他线程进入临界区。

其实做到上面三点只需要一把互斥锁( mutex ),将锁看作一个通行证,持有锁的线程才能进入临界区中执行代码,其他线程不持有锁,无法进入该临界区。

加锁本质就是让共享资源临界资源化,多个线程串行访问共享资源,从而保护共享资源的安全。

互斥锁本质上就是一个类(class pthread_mutex_t),可以构造对象pthread_mutex_t mutx,mutx就是互斥锁对象。

加锁可以让共享资源临界资源化,从而保护共享资源的安全,让多个线程串行访问共享资源。

mutex 接口

同创建线程一样,锁也需要创建,POSIX提供了锁的变量类型,如上面代码所示,其中mutext是互斥量

使用互斥量需要包含头文件:

#include<pthread.h>
初始化互斥量

初始化互斥量有两种方法:

  • 方法1,静态分配

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
    //初始化全局锁
    
  • 方法2,动态分配

    pthread_mutex_init

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict 
    attr); 
    //初始化局部锁
    
    //例:
    int main()
    {
        pthread_mutex_t mutex;
    	pthread_mutex_init(&mutex,nullptr);
    }
    

    参数

    • mutex:要初始化的互斥量
    • attr :NULL
销毁互斥量

销毁互斥量需要注意:

  • 使用 PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex)
//例:
//pthread_mutex_destroy(&mutex);
加锁和解锁

pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_lock(&mutex);

调用 pthread_ lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量, 那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_unlock(&mutex);

加锁解锁的原理

c/c++中加加和减减的操作并不是原子的,所以会导致多线程数据不一致的问题。

而为了能让加锁过程是原子的,在大多数体系结构了,都提供了swap或者xchange汇编指令,通过一条汇编指令来保证加锁的原子性。

pthread_mutex_lock(mutex);
//代码片段(原子的)
pthread_mutex_unlock(mutex);

在加锁与解锁间,就能保证中间的区域为临界区,里面的操作都是原子的

使用示例

再次模拟抢票系统

#include <iostream>
#include <unistd.h>
#include <vector>

#include "Thread.hpp"

using std::vector;

int ticket = 1000;                                 // 多线程可以直接访问
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化全局锁,也可以是局部的

void GetTicket(pthread_mutex_t* mutex)
{
    while (1)
    {
        pthread_mutex_lock(mutex); // 加锁
        // 在同一时刻,只允许一个线程申请锁成功! 多个线程申请锁失败,失败的线程在mutex上阻塞等待
        if (ticket > 0)
        {                 // 在加锁的时候,线程可能被切换,但是线程在切换时带走了[钥匙],别的进程也无法进行修改
            usleep(1000); // 1000微秒
            cout << " get a ticket: " << ticket-- << endl;
            pthread_mutex_unlock(mutex); // 解锁
        }
        else
        {
            pthread_mutex_unlock(mutex); // 解锁
            break;
        }
    }

    // 模拟抢票,实际情况还有很多后续代码
}

// 应用方的文件
string GetThreadName()
{
    static int number = 1;
    char name[64];
    snprintf(name, sizeof name, "Thread-%d", number++);
    return name;
}

void Print(int NUM)
{
    while (NUM--)
    {
        cout << "hello world" << endl;
        sleep(1);
    }
}

int main()
{
    pthread_mutex_t mutex;             //创建局部锁
    pthread_mutex_init(&mutex,nullptr); //初始化局部锁

    string name1 = GetThreadName();
    Thread<pthread_mutex_t*> t1(GetTicket, name1, &mutex);

    string name2 = GetThreadName();
    Thread<pthread_mutex_t*> t2(GetTicket, name2, &mutex);

    string name3 = GetThreadName();
    Thread<pthread_mutex_t*> t3(GetTicket, name3, &mutex);

    string name4 = GetThreadName();
    Thread<pthread_mutex_t*> t4(GetTicket, name4, &mutex);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();

    pthread_mutex_destroy(&mutex);
}
#pragma once

#include<pthread.h>

class Mutex
{
    pthread_mutex_t *_mutex;
public:
    Mutex(pthread_mutex_t *__mutex):_mutex(__mutex)
    {

    }
    ~Mutex()
    {

    }

    void Lock()
    {
        pthread_mutex_lock(_mutex);
    }

    void Unlock()
    {
        pthread_mutex_unlock(_mutex);
    }

};

class LockGuard
{
    Mutex _Mutex;
public:
    LockGuard(pthread_mutex_t* __mutex)
    :_Mutex(__mutex)
    {
        _Mutex.Lock();
    }

    ~LockGuard()
    {
        _Mutex.Unlock();
    }
};
  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ryan.Alaskan Malamute

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值