[Linux]------线程控制与互斥

news2024/9/29 13:40:14

文章目录

  • 前言
  • 一、进程VS线程
    • 空间共享
  • 二、线程控制
    • POSIX线程库
    • 创建线程
      • 获取线程ID
      • pthread_join
        • 线程异常
        • 第二个参数
    • 线程的局部存储
    • 线程的分离
    • exit()
  • 三、线程的互斥
    • 进城线程间的互斥相关背景概念
    • 互斥量mutex
      • 模拟抢票逻辑
      • 解决问题
      • 互斥量实现原理探究
      • 基于RAII机制锁的模拟实现
  • 四、可重入VS线程安全
    • 概念
    • 常见的线程不安全的情况
    • 常见的线程安全的问题
    • 常见不可重入的情况
    • 常见可重入的情况
    • 可重入与线程安全联系
    • 可重入与线程安全区别
  • 五、常见锁概念
    • 死锁
      • 代码重现死锁问题
    • 死锁的四个必要条件
    • 避免死锁
    • 避免死锁的算法
  • 总结


前言


正文开始!

一、进程VS线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据
    线程自有的属性
  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级

空间共享

进程的多个线程共享同一地址空间,因此Text Segment,Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享一下进程资源和环境:

  • 文件描述符表
  • 美中心好的处理方式(SIG_IGN,SIG_DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

二、线程控制

POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的
  • 要使用这些函数库,要通过引入头文件<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的"-lpthread"选项

创建线程

void* startRoutine(void* args)
{
    
    while(true)
    {
        cout<<"线程的正在运行..."<<endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    cout<<"new thread id: "<<tid<<endl;
    while(true)
    {
        cout<<"main thread 正在运行..."<<endl;
        sleep(1);
    }
    pthread_join(tid,nullptr);
    return 0;
}

在这里插入图片描述
tid的值为什么这么大呢?—>线程的ID

转为16进制打印后

在这里插入图片描述

获取线程ID

在这里插入图片描述

static void printTid(const char* name,const pthread_t& tid)
{
    printf("%s 正在运行,id : 0x%x\n",name,tid);
}
void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    
    while(true)
    {
        printTid(name,pthread_self());
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    while(true)
    {
        printTid("main thread",pthread_self());
        sleep(1);
    }
    pthread_join(tid,nullptr);
    return 0;
}

在这里插入图片描述
在这里插入图片描述
pthread_t的tid其实就是一个地址!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
线程的全部实现,并没有全部体现在OS内,而是OS提供执行流,具体的线程结构由库来进行管理。

库可以创建多个线程—>库也要管理线程!---->先描述在组织。

所以库里面就要有struct thread_info{pthread_t tid; void* stack;//私有栈…};

所以给用户
所以pthread_t是我们对应的用户级现成的控制结构体的起始地址!

所以主线程的独立栈结构用的就是地址空间中的栈区
新线程用的栈结构,用的就是库中提供的栈结构!

所以在Linux中,用户级线程库和内核的LWP是1:1的

pthread_join

在这里插入图片描述
就类似与我们之间讲过的fork()函数创建子进程,父进程需要waitpid()等待子进程退出。不等待就会造成内存泄漏等问题。

void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    int cnt=10;
    while(cnt--)
    {
        printTid(name,pthread_self());
        sleep(1);
    }
    cout<<"线程退出啦..."<<endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    sleep(5);
    cout<<"主线程运行结束,正在等待回收线程"<<endl;
    pthread_join(tid,nullptr);
    cout<<"回收线程成功...,我也退出啦!"<<endl;
    return 0;
}

在这里插入图片描述

线程异常

void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    int cnt=5;
    while(true)
    {
        printTid(name,pthread_self());
        sleep(1);
        if(!(cnt--))
        {
            int* p=nullptr;
            *p=10;//野指针问题
        }
    }
    cout<<"线程退出啦..."<<endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    while(true)
    {
        sleep(1);
    }
    pthread_join(tid,nullptr);
    return 0;
}

在这里插入图片描述
整个线程整体异常退出,线程异常=进程异常

线程会影响其他线程的运行—main thread----健壮性较低,鲁棒性

第二个参数

是一个输出型参数,获取新线程退出时候的退出码!
进程退出:

  • 代码跑完,结果正确
  • 代码跑完,结果不正确
  • 异常
    主线程为什么没有获取新线程退出时的信号呢?
    因为线程异常==进程异常
    在这里插入图片描述
    线程退出的方式
  1. return
void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    int cnt=5;
    while(true)
    {
        printTid(name,pthread_self());
        sleep(1);
        if(!(cnt--))
        {
            break;
        }
    }
    cout<<"线程退出啦..."<<endl;
    return (void*)10;
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    //void **retval是一个输出型参数
    void* ret=nullptr;
    pthread_join(tid,&ret);
    cout<<"main thread join success,*ret: "<<(long long)ret<<endl;
    return 0;
}

在这里插入图片描述
2. 调用pthread_exit()
在这里插入图片描述

void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    int cnt=5;
    while(true)
    {
        printTid(name,pthread_self());
        sleep(1);
        if(!(cnt--))
        {
            break;
        }
    }
    cout<<"线程退出啦..."<<endl;
    pthread_exit((void*)111);
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    //void **retval是一个输出型参数
    void* ret=nullptr;
    pthread_join(tid,&ret);
    cout<<"main thread join success,*ret: "<<(long long)ret<<endl;
    return 0;
}

在这里插入图片描述
3. 调用pthread_cancel()
在这里插入图片描述

void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    while(true)
    {
        printTid(name,pthread_self());
        sleep(1);
    }
    cout<<"线程退出啦..."<<endl;
}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    sleep(3);//代表main线程对应的工作
    pthread_cancel(tid);
    cout<<"new thread been canceled"<<endl;
    //void **retval是一个输出型参数
    void* ret=nullptr;
    pthread_join(tid,&ret);
    cout<<"main thread join success,*ret: "<<(long long)ret<<endl;
    return 0;
}

在这里插入图片描述
给线程发送取消请求,如果线程是被取消的,退出结果是:-1。

线程的局部存储

int global_value=100;
void* startRoutine(void* args)
{
    while(true)
    {
        cout<<"thread "<<pthread_self()
        <<" global_value: "<<global_value
        <<" &global_value: "<<&global_value
        <<endl;
        sleep(1);
    }

}

int main()
{
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_create(&t1,nullptr,startRoutine,(void*)"thread 1");
    pthread_create(&t1,nullptr,startRoutine,(void*)"thread 2");
    pthread_create(&t1,nullptr,startRoutine,(void*)"thread 3");

    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    pthread_join(t3,nullptr);
    return 0;
}

在这里插入图片描述
每个线程访问的是同一个变量!

如何让每个线程具有只属于自己的全局变量呢?

__thread int global_value=100;

在这里插入图片描述
每个线程就有了属于自己的变量了!—>局部存储!

获取线程的tid
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程的分离

在这里插入图片描述
关于pthread_join的返回值

在这里插入图片描述

int global_value = 100;
void *startRoutine(void *args)
{
    pthread_detach(pthread_self());
    std::cout<<"线程分离..."<<endl;
    while (true)
    {
        cout << "thread " << pthread_self()
             << " global_value: " << global_value
             << " &global_value: " << &global_value
             << " LWP: " << syscall(SYS_gettid)
             << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, nullptr, startRoutine,  (void *)"thread 1");
    pthread_create(&tid2, nullptr, startRoutine, (void *)"thread 2");
    pthread_create(&tid3, nullptr, startRoutine, (void *)"thread 3");
    sleep(2);
    int n = pthread_join(tid1, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << " : " << strerror(n) << endl;


    return 0;
}

在这里插入图片描述
所以我们在执行pthread_detach()分离线程以后就不能使用pthread_join()等待线程退出了!!

但是我们倾向于让主线程分离其他线程!

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, nullptr, startRoutine,  (void *)"thread 1");
    pthread_create(&tid2, nullptr, startRoutine, (void *)"thread 2");
    pthread_create(&tid3, nullptr, startRoutine, (void *)"thread 3");
    
    sleep(2);
    
    pthread_detach(tid1);
    pthread_detach(tid2);
    pthread_detach(tid3);
    return 0;
}

线程可以立即分离和延后分离----线程活着----意味着我们不在关心这个线程的死活了—可以理解为线程退出的第四种方式!(延后分离)

新线程分离,但是主线程先退出(进程退出!)—一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)

exit()

pthread_exit()是退出该线程!
任何一个线程调用exit(),都表示整个进程退出!!!

三、线程的互斥

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

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

在这里插入图片描述

互斥量mutex

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

模拟抢票逻辑

//临界资源
// int 票数计数器
int tickets = 1000; //临界资源,可能会因为共同访问,可能会造成数据不一致问题
void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);
    while (true)
    {
        if (tickets > 0)
        {
            usleep(10000);
            cout << name << "抢到了票,票的编号: " << tickets << endl;
            tickets--;
            usleep(123);//模拟其他业务逻辑的执行
        }
        else
        {
            //票抢到几张,就算没有了呢?--->0
            cout << name << " 已经放弃抢票了,因为没有了..." << endl;
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");
    pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");
    pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");
    pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");
    
    int n = pthread_join(tid1, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid4, nullptr);
    cout << n << " : " << strerror(n) << endl;
    return 0;
}

正常情况下,票抢到0张的时候是不是就结束了呢?请看以下结果

在这里插入图片描述
这种情况存在偶然性,但是真正抢票的时候是不能发生的!!!

因为tickets–:是由一条语句完成的吗???—>并不是,在底层汇编语句的实现,要至少被翻译称为三条语句!!!

在这里插入图片描述
tickets–:

  1. load tickets to reg;
  2. reg–;
  3. write reg to tickets;

在上面三条语句的任何地方,线程都有可能被切换走!!!

CPU的寄存器是被所有的执行流共享的,但是寄存器里面的数据是属于当前执行流的上下文数据的!(线程被切换的时候,需要保存上下文;线程被换回的时候,需要恢复上下文)

**为了避免以上情况的发生,我们应该保证访问临界区的操作是原子的!**所以访问临界区的操作我们可以给他加锁!

解决问题

在这里插入图片描述
在这里插入图片描述

//临界资源
// int 票数计数器
int tickets = 1000; //临界资源,可能会因为共同访问,可能会造成数据不一致问题
pthread_mutex_t mutex;
void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);
    while (true)
    {
        //临界区,只要对临界区加锁,而且加锁的粒度越细越好
        //加锁的本质是让线程执行临界区代码串行化
        //加锁是一套规范法,通过临界区对临界资源进行访问的时候,要加就都要加
        pthread_mutex_lock(&mutex);
        if (tickets > 0)
        {
            usleep(10000);
            cout << name << "抢到了票,票的编号: " << tickets << endl;
            tickets--;
            pthread_mutex_unlock(&mutex);
            usleep(123);//模拟其他业务逻辑的执行
        }
        else
        {
            //票抢到几张,就算没有了呢?--->0
            cout << name << " 已经放弃抢票了,因为没有了..." << endl;
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_mutex_init(&mutex, nullptr);
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");
    pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");
    pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");
    pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");

    int n = pthread_join(tid1, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << " : " << strerror(n) << endl;
    n = pthread_join(tid4, nullptr);
    cout << n << " : " << strerror(n) << endl;

    pthread_mutex_destroy(&mutex);
    return 0;
}

在这里插入图片描述

锁保护的是临界区,换言之任何线程执行我们临界区代码,访问临界资源。都必须先申请锁,前提是都必须看到锁
这把锁,本身不就也是临界资源吗??锁的设计者早就想到了
pthread_mutex_lock:竞争和申请锁的过程,就是原子的!!

如果锁是main创建的并且初始化的,线程想要获得锁和线程名可以使用结构体传参!
结构体传参

int tickets = 1000;
#define NAMESIZE 100
typedef struct threadData
{
    char _name[NAMESIZE];
    pthread_mutex_t *_mutex;
} threadData;

void *startRoutine(void *args)
{
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->_name;
    pthread_mutex_t *mutex = td->_mutex;
    while (true)
    {
        pthread_mutex_lock(mutex); //如果申请不到,就阻塞进程
        if (tickets > 0)
        {
            usleep(1000);
            cout << name << " get a ticket: " << tickets << endl;
            tickets--;
            pthread_mutex_unlock(mutex);
            //你还有其他的事情做
            usleep(500);
        }
        else
        {
            pthread_mutex_unlock(mutex);
            break;
        }
    }
}
int main()
{
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_t t4;

    threadData* td=new threadData();
    strcpy(td->_name,"thread 1");
    td->_mutex=&mutex;
    pthread_create(&t1, nullptr, startRoutine, (void *)td);
    pthread_create(&t2, nullptr, startRoutine, (void *)&mutex);
    pthread_create(&t3, nullptr, startRoutine, (void *)&mutex);
    pthread_create(&t4, nullptr, startRoutine, (void *)&mutex);

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    delete(td);
    return 0;
}

在这里插入图片描述
也可以正常的实现抢票的逻辑!!!

难道在加锁的临界区里面,就没有线程切换了吗?是不是加锁==不会被切换?

当然是完全可以!因为线程执行的加锁解锁等对应的也是代码,线程在任意代码处都可以被切换的,但是线程加锁是原子的—>这个锁,要么你拿到了,要么没有拿到

在我被切走的时候,绝对不会有线程进入临界区!!!!---->因为线程进入临界区需要先申请锁,但是锁现在被我抱着跑了(即便我没有被调度)—>新线程申请不到,只能被挂起了!!!

所以一旦一个线程持有了锁,该线程根本就不担心任何切换问题!!!
对于其他线程而言,该线程访问临界区,只有没有进入和访问完毕两种状态!!!这样才对其他线程有意义!!!

所以这就要求我们尽量不要在临界区里面做一些耗时的事情!!

互斥量实现原理探究

  • 经过上面抢票的例子,大家肯定能意识到单纯的tickets++不是原子的,有可能会出现数据不一致问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作业是把寄存器和内存单元的数据相交换,由于只有一条指令,保证原子性,即使是多处理平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。我们来做一个lock的伪代码帮助大家理解:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
线程b在线程a执行swap或者exchange交换前切入线程后就可以获得锁,之后线程a在切换也只是吧线程中mutex的0在来回切换,执行if判断也就只能挂起等待了!

本质:将数据从内存读入存储器,本质是将数据从共享变成线程私有!
1就如同令牌一般

解锁的过程就是把1在写入内存中的变量mutex即可!

基于RAII机制锁的模拟实现

log.hpp

/*
 * @Author: hulu 2367598978@qq.com
 * @Date: 2022-11-17 16:46:55
 * @LastEditors: hulu 2367598978@qq.com
 * @LastEditTime: 2022-11-17 16:49:18
 * @FilePath: /2022_11_16/lock.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }

    void lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }

private:
    pthread_mutex_t _lock;
};

class LockGuard
{
public:
    LockGuard(Mutex* mutex)
        : _mutex(mutex)
    {
        _mutex->lock();
        std::cout<<"加锁成功..."<<std::endl;
    }
    ~LockGuard()
    {
        _mutex->unlock();
        std::cout<<"解锁成功..."<<std::endl;
    }

private:
    Mutex* _mutex;
};

main.cc

Mutex mutex;
int tickets = 1000;
//函数本质就是一个代码块,
bool getTickets()
{
    bool ret=false;//函数的局部变量,在栈上保存,线程具有独立的栈结构,每个线程各自一份
    LockGuard lock_guard(&mutex);//局部对象的声明生命周期是随代码块的
    if (tickets > 0)
    {
        usleep(1000);
        cout << "thread " <<pthread_self()<< " get a ticket: " << tickets << endl;
        tickets--;
        ret=true;
    }
    return ret;
}

void* startRoutine(void* args)
{
    const char* name=static_cast<const char*>(args);
    while(true)
    {
        if(!getTickets())
        {
            break;
        }
        cout<<name<<" get tickets success "<<endl;
        //其他事情要做
        usleep(100);
        
    }
}
int main()
{
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_t t4;

    pthread_create(&t1, nullptr, startRoutine, (void *)"thread 1");
    pthread_create(&t2, nullptr, startRoutine, (void *)"thread 2");
    pthread_create(&t3, nullptr, startRoutine, (void *)"thread 3");
    pthread_create(&t4, nullptr, startRoutine, (void *)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);
    return 0;
}

在这里插入图片描述

四、可重入VS线程安全

概念

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个进程还没有执行玩,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行的结果不会出现任何不同或者问题,则该函数被称为可重入函数,否则,是不可重入函数。

常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全的函数

常见的线程安全的问题

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作的
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆得
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

常见可重入的情况

  • 不使用全局变量或者静态变量
  • 不使用malloc或者nwe开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或者全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
  • 如果将临界资源的访问加上锁,贼这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

五、常见锁概念

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待的状态

代码重现死锁问题

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include"lock.hpp"
using namespace std;

pthread_mutex_t mutexA=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB=PTHREAD_MUTEX_INITIALIZER;

void* startRoutine1(void* args)
{
    while(true)
    {
        pthread_mutex_lock(&mutexA);
        sleep(1);
        pthread_mutex_lock(&mutexB);
        cout<<"我是线程1,我的tid: "<<pthread_self()<<endl;

        pthread_mutex_unlock(&mutexA);
        pthread_mutex_unlock(&mutexB);
    }

}
void* startRoutine2(void* args)
{
    while(true)
    {
        pthread_mutex_lock(&mutexB);
        sleep(1);
        pthread_mutex_lock(&mutexA);
        cout<<"我是线程2,我的tid: "<<pthread_self()<<endl;

        pthread_mutex_unlock(&mutexB);
        pthread_mutex_unlock(&mutexA);
    }

}
int main()
{
    pthread_t t1,t2;
    pthread_create(&t1,nullptr,startRoutine1,nullptr);
    pthread_create(&t1,nullptr,startRoutine2,nullptr);

    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    return 0;
}

在这里插入图片描述
上面打的东西乱是缺少访问控制。

在这里插入图片描述
此时线程还未退出,但是卡在这里不执行了。这就是死锁问题!!!

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁的算法

  • 死锁检测算法(了解)
  • 银行家算法(了解)

总结

(本章完!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 线程控制 —— 线程清理 pthread_cleanup_push

主线程可以通道 pthread_cancel 主动终止子线程&#xff0c;但是子线程中可能还有未被释放的资源&#xff0c;比如malloc开辟的空间。如果不清理&#xff0c;很有可能会造成内存泄漏。 // 子线程回调函数 void* thread_run(void* args) {int* p (int*)malloc(100); // 动…

中小企业选择ERP系统时应关注的10个关键功能

现代ERP系统是帮助企业实现提高生产力、增加盈利能力和提高竞争力的目标的好帮手。该类软件旨在满足中小企业不断增长的业务需求&#xff0c;可确保整个企业的健康发展。 每天都有新的ERP功能和应用程序进入市场&#xff0c;如何明智选择至关重要。以下是中小企业在选择现代ERP…

Elasticsearch_第2章_ elasticsearch基础

Elasticsearch_第2章_ elasticsearch基础 文章目录Elasticsearch_第2章_ elasticsearch基础0.学习目标1.DSL查询文档1.1.DSL查询分类1.2.全文检索查询1.2.1.使用场景1.2.2.基本语法1.2.3.示例1.2.4.总结1.3.精准查询1.3.1.term查询1.3.2.range查询1.3.3.总结1.4.地理坐标查询1.…

SSM+JSP实现《吃货联盟外卖系统》

&#x1f345;程序员小王的博客&#xff1a;程序员小王的博客 &#x1f345;程序员小王的资源博客&#xff1a;http://wanghj.online/ &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 如有编辑错误联系作者&#xff0c;如果有比较好的文章欢迎…

上海亚商投顾:沪指冲高回落 纺织服装股午后集体走强

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪沪指今日缩量震荡&#xff0c;以中字头为首的权重股走低&#xff0c;上证50午后一度跌近1%&#xff0c;创业板指较为…

秋招|阿里测试开发岗面经(一共七次面试)

三月份的时候投了阿里的实习&#xff0c;然后基本上是一周面一次&#xff0c;前前后后一个月。实习通过了&#xff0c;但是后面因为有事&#xff0c;所以没能去成暑期实习&#xff0c;部门leader人很好&#xff0c;说是可以在秋招的时候再补上终面&#xff0c;于是就有了一共七…

jQuery easyui源码赏析

引子 jQuery未过时&#xff0c;在一些中小网站中&#xff0c;jQuery还是发挥着瑞士军刀的作用。我们不能为了前后端分离而分离&#xff0c;一些很简单的需求&#xff0c;很简单的页面&#xff0c;freemarkerjQuerybootstrap就能搞掂&#xff0c;奈何一定要搬动vue和react这些大…

[附源码]计算机毕业设计影评网站系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【雷达回波】基于matlab天气观测极化雷达回波仿真【含Matlab源码 2252期】

⛄一、天气观测极化雷达回波仿真简介 本示例展示了如何模拟满足天气观测要求的极化多普勒雷达回波。雷达在天气观测、灾害检测、降水分类和量化以及预报方面发挥着关键作用。此外&#xff0c;极化雷达以前所未有的质量和信息提供多参数测量。此示例演示如何模拟扫描分布式天气…

Java项目:ssm+jsp实现手机WAP版外卖订餐系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 这是一款由jspssm框架&#xff08;spring、springMVC、mybaits&#xff09;实现的手机WAP版外卖订餐系统。 本系统前台页面是手机端的&#xf…

如何发布一个 npm 包

前言 npm&#xff08;node package manager&#xff09;作为 Node.js 的包管理工具&#xff0c;让世界各地的 JavaScript 开发者方便复用、分享代码以及造轮子&#xff1b;本文将介绍如何发布一个 npm 包&#xff0c;以及使用工具来自动化管理发布 npm 包&#xff1b;本文总览…

Java代码审计——WebGoat CSRF (上)

目录 前言&#xff1a; &#xff08;一&#xff09;CSRF 0x01、简单介绍 0x02、实际案例 1&#xff0e;对 Referer 过滤不严导致的 CSRF 漏洞 2&#xff0e;token 可重用导致 CSRF 漏洞 3、webGoat中的CSRF 0x03 防御 3.1 STP 3.2 检查 Referer 字段 3.3 检查 Referer…

GMO Research 2022年旅游调查:旅游业有望强劲增长

GMO Research (TOKYO: 3695)最近进行的一项旅行调查显示&#xff0c;随着边境再次开放&#xff0c;亚洲正在逐渐恢复正常的旅行模式。尽管该地区仍没有达到疫情前水平&#xff0c;旅行者仍持谨慎态度&#xff0c;但他们对海外旅行的兴趣显著增加。 为了解旅行模式和旅行意愿&a…

Intel OpenVINO 安装显卡驱动

背景&#xff1a; 使用集合诚KMDA-3301 OpenVINO CPU和GPU 算法加速&#xff0c;用GPU加速时&#xff0c;调动不起来。写下解决过程&#xff0c;以备后用。 过程&#xff1a; 调动GPU 报错&#xff1a; terminate called after throwing an instance of InferenceEngine::G…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校师生党建管理系统4d8du

最近大四学弟学妹们开始准备设计了&#xff0c;有一些问题问我&#xff0c;比如设计怎么做&#xff0c;有没有模板等等吧&#xff0c;大家都没有去学校&#xff0c;老师都是通过远程指导的&#xff0c;答辩也是远程答辩&#xff0c;这种情况下同学们不在一起&#xff0c;可能碰…

day13【代码随想录】环形链表II、环形链表、快乐数、各位相加、丑数、丑数||

文章目录一、环形链表 II&#xff08;力扣142&#xff09;二、环形链表&#xff08;力扣141&#xff09;三、快乐数&#xff08;力扣202&#xff09;四、各位相加&#xff08;力扣258&#xff09;五、丑数&#xff08;力扣263&#xff09;六、丑数||&#xff08;力扣264&#x…

JavaScript:File API和Blob API

web应用的痛点就是不能操作计算机上的文件。File API和Blob API可以安全访问到客户端上的文件。 File类型 现在我们可以在html表单中直接访问文件&#xff0c;比如&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"U…

PaddleOCR服务部署-并通过Java进行调用

上一篇讲了PaddleOCR的简单使用&#xff0c;但是最终的目的肯定是要将它进行服务部署方便我们调用的&#xff0c;这里介绍一下他的服务部署方式 选择部署方式 官方推荐有以下几种&#xff1a; Python 推理 C 推理 Serving 服务化部署&#xff08;Python/C&#xff09; Paddle…

电影寒冬之下,票房靠“主旋律”能撑住场吗?《扫黑行动》仍在重播

春节将近&#xff0c;各大院线陆陆续续公布了春节档将要上映的影片档期&#xff0c;小伙伴们是不是也对近期热门的影片有了兴趣&#xff0c;想要一饱眼福了呢。下面是小编根据网络公布的数据进行报表数据处理分析后得到的数据可视化图&#xff0c;展示了近期一些热门影片的情况…

数据库专辑--WITH CHECK OPTION的用法

系列文章 C#底层库–数据库访问帮助类&#xff08;MySQL版&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/126886379 文章目录系列文章前言一、概念介绍二、测试用例2.1 数据准备2.2 数据查询2.3 修改视图&#xff0c;加上WITH CHECK O…