Linux——多线程互斥(下)

简介: Linux——多线程互斥(下)

加锁和解锁的原理

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

现在我们把lock和unlock的伪代码改一下:

假设现在是线程A运行,线程A进行了申请加锁,内存中的int当中的1就是锁。

首先让0放进CPU中的寄存器%al当中,然后将内存中的1%al中的0交换。

这里交换是一条汇编完成的。

这个时候,如果突然时间片到了,线程B换了上来,线程A就要带着自己的上下文走。

然后线程B从头开始,先将0放入%al,然后交换:

这里继续向下执行语句,发现寄存器%al中的内容并不大于0,走第二条语句,线程B就被挂起等待了。

然后线程A又切换回来继续向下执行:

这就是为什么当前线程申请锁之后其他线程无法申请锁!

解锁的过程就是将%al的1移动到内存中:

锁的封装

因为C语言很多接口是不兼容C++的,所以我们要想办法设计让锁的接口兼容C++。

#pragma once
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <memory>
using namespace std;
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock_p = nullptr):_lock_p(lock_p)
    {}
    void lock()
    {
        if(_lock_p) pthread_mutex_lock(_lock_p);
    }
    void unlock()
    {
        if(_lock_p) pthread_mutex_unlock(_lock_p);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *_lock_p;
};
class LockGuard//这里像智能指针一样,自动解锁
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        _mutex.lock();
    }
    ~LockGuard()
    {
        _mutex.unlock();
    }
private:
    Mutex _mutex;
};
#pragma once
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <memory>
using namespace std;
class Thread;//声明
class Context//上下文,相当于一个大号的结构体
{
public:
    Thread *this_;
    void* args_;
public:
    Context():this_(nullptr),args_(nullptr)
    {}
    ~Context()
    {}
};
class Thread
{
    typedef function<void* (void*)> func_t;
public:
    //这里需要加一个静态,因为不加静态就是类成员函数,还有一个隐藏的this指针,也就说明这等于前面有一个缺省参数
    //所以在类内创建线程,想让对应的线程执行方法需要在方法前面加一个static
    static void* start_routine(void* args)
    {
        //但是静态方法不能调用成员方法或者成员变量,这里可以设置一个上下文
        Context* ctx = static_cast<Context*>(args);
        void* ret = ctx->this_->run(ctx->args_);//这里让自身去调用这个方法
        delete ctx;
        return ret;
    }
    void* run(void* args)
    {
        return _func(args);//调用该函数
    }
    Thread(func_t func,void* args,int num):_func(func),_args(args)
    {
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "thread_%d", num);
        _name = buffer;
        Context* ctx = new Context();
        ctx->this_ = this;
        ctx->args_ = _args;//这里是将自身的部分数据传给ctx
        int n = pthread_create(&_tid, nullptr, start_routine, ctx);//这里要通过调用函数来转化,直接传func是不行的,因为类型是C++的类,不是C语言的类
        assert(n==0);
        (void)n;
    }
    void join()
    {
        int n = pthread_join(_tid,nullptr);
        assert(n==0);
        (void)n;
    }
    ~Thread()
    {}
private:
    string _name;//线程名字
    pthread_t _tid;//线程id
    func_t _func;//线程调用的函数
    void* _args;//传给函数的参数
};
#include "Thread.hpp"
#include "MUtex.hpp"
int tickets = 1000;//票数
class ThreadData
{
public:
    ThreadData(const string& threadname, pthread_mutex_t *mutex_p):_threadname(threadname),_mutex_p(mutex_p)
    {}
    ~ThreadData()
    {}
public:
    string _threadname;
    pthread_mutex_t *_mutex_p;
};
void* thread_run(void* args)
{
    ThreadData* p = static_cast<ThreadData*>(args);
    LockGuard lockGuard(p->_mutex_p);//这里会自动加锁解锁
    while(true)
    {
      {//这里的域是为了避免对下面的usleep进行加锁
          if(tickets > 0)
          {
              usleep(1234);//1秒=1000毫秒=1000000微秒
              cout << p->_threadname << "用户正在抢票:"<< tickets-- <<endl;
          }
          else
          {
              break;
          }
        }
        usleep(1234);//模拟抢完票形成一个订单,这里也就等于阻止了竞争力强的线程,让竞争力强的到后面排队去
    }
}
int main()
{
    pthread_mutex_t lock;
    pthread_mutex_init(&lock, nullptr);//初始化锁,第二个参数设为nullptr就可以
    vector<pthread_t> arr(4);
    for(int i = 0;i < 4; i++)
    {
        char buffer[64];
        snprintf(buffer,sizeof(buffer),"thread %d",i+1);
        ThreadData* p = new ThreadData(buffer, &lock);
        pthread_create(&arr[i], nullptr, thread_run, p);
    }
    for(const auto& e:arr)
    {
        pthread_join(e,nullptr);
    }
    pthread_mutex_destroy(&lock);//解锁
    return 0;
}

这种风格叫做RAII加锁。

可重入与线程安全

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

常见的线程不安全的情况

不保护共享变量的函数

函数状态随着被调用,状态发生变化的函数

返回指向静态变量指针的函数

调用线程不安全函数的函数

常见的线程安全的情况

每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。

类或者接口对于线程来说都是原子操作。

多个线程之间的切换不会导致该接口的执行结果存在二义性。

常见不可重入的情况

调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。

调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

可重入函数体内使用了静态的数据结构。

可重入与线程安全联系

函数是可重入的,那就是线程安全的。

函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。

如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

可重入函数是线程安全函数的一种。

线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生。

死锁,因此是不可重入的。

死锁

死锁的概念与条件

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

死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用。

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

上面代码有个例子,申请了两次锁,就等于自己本来有锁,又申请一次之后等于将自己挂起,这样谁也申请不到锁了。

避免死锁

破坏死锁的四个必要条件。

加锁顺序一致。

避免锁未释放的场景。(也就是用完锁一定要释放)

资源一次性分配。(不要到处给锁分配资源,不然看起来很乱,就容易造成死锁)

这里要注意一下,当前线程的锁可以被别的线程释放,上面的汇编语言释放锁的逻辑就说明了这一点。

避免死锁算法

死锁检测算法

银行家算法

注意:平时尽量不要用锁。

相关文章
|
9月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
7月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
279 67
|
9月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
162 26
|
9月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
170 17
|
算法 Unix Linux
linux线程调度策略
linux线程调度策略
333 0
|
12月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
177 6
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
214 0
Linux C/C++之线程基础
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
存储 设计模式 NoSQL
Linux线程详解
Linux线程详解
|
缓存 Linux C语言
Linux线程是如何创建的
【8月更文挑战第5天】线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。

热门文章

最新文章