协程定时互斥锁


// Copyright (c) 2020, Tencent Inc.
// All rights reserved.

#ifndef TRPC_FIBERS_TIMED_MUTEX_H
#define TRPC_FIBERS_TIMED_MUTEX_H

#include <chrono>  // std::chrono
#include <atomic>  // std::atomic
#include <cstddef>
#include <string>


#include "trpc/log/trpc_log.h" // TRPC_ASSERT
#include "trpc/util/likely.h"
#include "trpc/util/chrono/chrono.h"
#include "trpc/coroutine/fiber_mutex.h"
#include "trpc/runtime/threadmodel/fiber/detail/waitable.h"   // WaitBlock Waitable  等待队列实现




namespace trpc::fiber::detail {


class  FiberTimedMutex {
    // typedef std::chrono::steady_clock  steady_clock_t; 
private:
    trpc::fiber::detail::Waitable              wait_queue_;             // 等待队列                   
    trpc::fiber::detail::FiberEntity*          owner_{nullptr};         // 锁
    trpc::Spinlock                             splk_;                   // 自旋锁

    bool try_lock_until_( std::chrono::steady_clock::time_point const& timeout_time) noexcept;

public:
    FiberTimedMutex() = default;

    ~FiberTimedMutex() {
        TRPC_ASSERT( nullptr == owner_);           // 析构的时候不能有fiber持有锁。为true时无异常。否则异常
        // TRPC_ASSERT( wait_queue_.Empty());      // wait_queue_析构时会判断等待队列是否为空
    }

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

    void lock();

    bool try_lock();

    template< typename Clock, typename Duration >
    bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time_) {
        std::chrono::steady_clock::time_point timeout_time = std::chrono::steady_clock::now() + ( timeout_time_ - Clock::now());
        return try_lock_until_( timeout_time);
    }

    template< typename Rep, typename Period >
    bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration) {
        return try_lock_until_( std::chrono::steady_clock::now() + timeout_duration);
    }

    void unlock();
};


class fiber_error : public std::system_error {
public:
    explicit fiber_error( std::error_code ec) :
        std::system_error{ ec } {
    }

    fiber_error( std::error_code ec, const char * what_arg) :
        std::system_error{ ec, what_arg } {
    }

    fiber_error( std::error_code ec, std::string const& what_arg) :
        std::system_error{ ec, what_arg } {
    }

    ~fiber_error() override = default;
};

class lock_error : public fiber_error {
public:
    explicit lock_error( std::error_code ec) :
        fiber_error{ ec } {
    }

    lock_error( std::error_code ec, const char * what_arg) :
        fiber_error{ ec, what_arg } {
    }

    lock_error( std::error_code ec, std::string const& what_arg) :
        fiber_error{ ec, what_arg } {
    }
};

}




#endif // TRPC_FIBERS_TIMED_MUTEX_H
// Copyright (c) 2020, Tencent Inc.
// All rights reserved.

#include "trpc/coroutine/fiber_timed_mutex.h"

#include <system_error>  // std::make_error_code
#include <iostream>  

#include "trpc/coroutine/fiber_condition_variable.h"
#include "trpc/runtime/threadmodel/fiber/detail/fiber_entity.h"
#include "trpc/runtime/threadmodel/fiber/detail/scheduling_group.h"
#include "trpc/util/delayed_init.h"
#include "trpc/util/ref_ptr.h"

// using trpc::fiber::detail::AsyncWaker;

namespace trpc::fiber::detail {

class AsyncWaker {
 public:
  AsyncWaker(SchedulingGroup* sg, FiberEntity* self, WaitBlock* wb)
      : sg_(sg), self_(self), wb_(wb) {}

  ~AsyncWaker() { TRPC_CHECK_EQ(timer_, std::uint64_t(0), "Have you called `Cleanup()`?"); }

  void SetTimer(std::chrono::steady_clock::time_point expires_at) {
    wait_cb_ = MakeRefCounted<WaitCb>();
    wait_cb_->waiter = self_;
    // Create a callback function
    auto timer_cb = [wait_cb = wait_cb_ /* ref-counted */, wb = wb_](auto) {
      std::scoped_lock lk(wait_cb->lock);
      if (!wait_cb->awake) {
        if (wb->satisfied.exchange(true, std::memory_order_relaxed)) {
          return;
        }
        wait_cb->waiter->scheduling_group->Resume(  // 异步回调函数会恢复因此被挂起的fiber
          wait_cb->waiter, std::unique_lock(wait_cb->waiter->scheduler_lock));
      }
    };
    timer_ = sg_->CreateTimer(expires_at, timer_cb);
    sg_->EnableTimer(timer_);
  }

  void Cleanup() {
    sg_->RemoveTimer(std::exchange(timer_, 0));
    {
      std::scoped_lock _(wait_cb_->lock);
      wait_cb_->awake = true;
    }
  }

 private:
  struct WaitCb : RefCounted<WaitCb> {
    Spinlock lock;
    FiberEntity* waiter;
    bool awake = false;
  };

  SchedulingGroup* sg_;
  FiberEntity* self_;
  WaitBlock* wb_;
  RefPtr<WaitCb> wait_cb_;
  std::uint64_t timer_ = 0;
};


bool // The current fiber attempts to acquire the fiber_timed_lock, blocking until the specified time to acquire ownership. If the specified time has elapsed, the behavior is the same as try_lock().
FiberTimedMutex::try_lock_until_( std::chrono::steady_clock::time_point const& timeout_time) noexcept 
{
    for (int i=0;i<1000;i++) // keep trying to lock
    {
        FiberEntity* current = GetCurrentFiberEntity(); // get the current fiber context
        std::unique_lock slk(current->scheduler_lock);  // we need to hold `scheduler_lock` until we finished context swap.
        if ( nullptr == owner_)
        {   // if equql to nullptr, locked
            owner_ = current;
            return true;
        }

        SchedulingGroup* sg = current->scheduling_group;
        WaitBlock wb = {.waiter = current};
        DelayedInit<AsyncWaker> awaker;
        if (i == 0)
        {
            // Add to the waiting queue.
            TRPC_CHECK(wait_queue_.AddWaiter(&wb));

            // Set asynchronous timed tasks.
            if (timeout_time!= std::chrono::steady_clock::time_point::max())
            {
                awaker.Init(sg, current, &wb);
                awaker->SetTimer(timeout_time);
            }
        }

        // If the lock is not obtained, suspend the current fiber and restore other fibers
        sg->Suspend(current, std::move(slk));

        // Determine whether to time out after fiber recovery
        if (std::chrono::steady_clock::now() < timeout_time)
        {
            // wait_queue_.TryRemoveWaiter(&wb);  // prevent duplicate insertion
            if (awaker)
                awaker->Cleanup();
            continue;
        }

        // Has timed out, removed from the waiting queue
        wait_queue_.TryRemoveWaiter(&wb);
        if (awaker)
        {
            awaker->Cleanup();
        }
        return false;
    }
}

void // The current fiber blocks until ownership is acquired. Returns true if the current fiber takes ownership, false otherwise.
FiberTimedMutex::lock()
{
    while ( true)
    {
        auto current = GetCurrentFiberEntity();
        // Prohibit the fiber that has been locked, and lock it again to cause deadlock
        if ( TRPC_UNLIKELY( current == owner_) ) 
        {
            throw lock_error{
                    std::make_error_code( std::errc::resource_deadlock_would_occur),
                    "trpc fiber: a deadlock is detected"
                };
        }

        std::unique_lock slk(current->scheduler_lock);
        if ( nullptr == owner_) 
        {
            owner_ = current;
            return;
        }

        WaitBlock wb = {.waiter = current};
        TRPC_CHECK(wait_queue_.AddWaiter(&wb));
        // Suspend the current fiber and resume other fiber

        current->scheduling_group->Suspend(current, std::move(slk));
    }
}

bool  // Try to lock, return true if successful, false if failed.
FiberTimedMutex::try_lock()
{
    FiberEntity* current = GetCurrentFiberEntity();
    std::unique_lock slk(splk_);
    if ( TRPC_UNLIKELY( current == owner_) )
    {
        throw lock_error{
                std::make_error_code( std::errc::resource_deadlock_would_occur),
                "trpc fiber: a deadlock is detected" };
    }

    if ( nullptr == owner_) 
        owner_ = current;
    slk.unlock();
    // let other fiber release the lock
    current->scheduling_group->Yield(current);  // Can not get the lock, call other fibers immediately
    return current == owner_;
}

void  // Release the ownership of fiber_timed_mutex.
FiberTimedMutex::unlock()
{  
    FiberEntity* current = GetCurrentFiberEntity();
    std::unique_lock slk(splk_);
    if ( TRPC_UNLIKELY( current != owner_) ) 
    {   // Who locks, who unlocks
        throw lock_error{
                std::make_error_code( std::errc::operation_not_permitted),
                "TRPC fiber: no  privilege to perform the operation"
            };
    }
    owner_ = nullptr;

    // Notify a fiber waiting for a lock
    auto fiber = wait_queue_.WakeOne();
    if (!fiber)
        return;
    fiber->scheduling_group->Resume(fiber, std::unique_lock(fiber->scheduler_lock));
}

}

#include "trpc/coroutine/fiber_timed_mutex.h"

#include <chrono>
#include <iostream>
#include <stdexcept>
#include <time.h>

#include "gtest/gtest.h" // 谷歌命令解析工具

#include "trpc/coroutine/fiber.h"
#include "trpc/coroutine/testing/fiber_runtime.h"
#include "trpc/util/random.h"

using namespace std;

typedef std::chrono::nanoseconds  ns;  // 纳秒
typedef std::chrono::milliseconds ms;  // 毫秒 s , ms, us, ns


namespace trpc::fiber::detail {
Fiber::Attributes attr{.launch_policy = fiber::Launch::Dispatch};

// 测试fiber的调用顺序
TEST(FiberTimedMutexTest, test0) {
    RunAsFiber([&] {
        // std::cout << "\nget lock back" << std::endl;

        Fiber f(attr,[]{
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            // std::cout << "prapare get the lock" << std::endl;
            FiberSleepFor(250 * 1ms);
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            ns d = t1 - t0 - ms(250);
            // std::cout << d.count() << "ms" << std::endl;
        });

        // std::cout << "come back" << std::endl;
        f.Join();  // 阻塞等待f完成

    });
}

// 测试lock()
TEST(FiberTimedMutexTest, test1) {
    RunAsFiber([&] {
        FiberTimedMutex m;
        m.lock(); // 外层fiber先上锁

        Fiber f(attr,[&m]{
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            m.lock();  // 内层fiber再上锁
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            m.unlock();
            ns d = t1 - t0 - ms(250);
            // std::cout << d.count() << "ns" << std::endl;
            TRPC_CHECK(d > ns(0)); // within 2.5 ms
        });

        FiberSleepFor(250 * 1ms);
        m.unlock();
        f.Join();  // 阻塞等待f完成

    });
}

// 测试try_lock()
TEST(FiberTimedMutexTest, test2) {
    RunAsFiber([&] {
        FiberTimedMutex m;
        m.lock(); // 外层fiber先上锁

        Fiber f(attr,[&m]{
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            while ( ! m.try_lock() ); // 必须返回false
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            m.unlock();
            ns d = t1 - t0 - ms(250);
            // std::cout << d.count() << "ns" << std::endl;
            TRPC_CHECK(d > ns(0)); // within 2.5 ms
        });

        FiberSleepFor(300 * 1ms);
        m.unlock();
        f.Join();  // 阻塞等待f完成

    });
}

// 测试try_lock_for
TEST(FiberTimedMutexTest, test3) {
    RunAsFiber([] {
        FiberTimedMutex m;
        Fiber f(attr,[&m]{
            m.lock();
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            TRPC_CHECK ( !m.try_lock_for(ms(1000))); // 必须返回false
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            m.unlock();
        });
        f.Join();
    });
}

// 测试try_lock_until
TEST(FiberTimedMutexTest, test4) {
    RunAsFiber([] {
        FiberTimedMutex m;
        Fiber f(attr,[&m]{
            m.lock();
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            TRPC_CHECK(m.try_lock_until(std::chrono::steady_clock::now() + ms(1000)) == false);
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            m.unlock();
            ns d = t1 - t0 - ms(250);
            TRPC_CHECK(d > ns(0)); // within 5ms
        });
        f.Join();
    });
}

// 测试try_lock_until
TEST(FiberTimedMutexTest, test5) {
    RunAsFiber([] {
        FiberTimedMutex m;
        m.lock();
        Fiber f(attr,[&m]{
            std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
            TRPC_CHECK( m.try_lock_until(std::chrono::steady_clock::now() + ms(250)) == true );
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            m.unlock();
            ns d = t1 - t0;
            TRPC_CHECK(ns(0) < d); // within 5ms
        });
        FiberSleepFor(100 * 1ms);
        m.unlock();
        f.Join();
    });
}

// 测试多个fiber同时请求锁,模拟多个fiber同事请求同一个锁
TEST(FiberTimedMutexTest, test6) {
    RunAsFiber([] {
        FiberTimedMutex m;

        auto func = [&m]{
            if (m.try_lock_until(std::chrono::steady_clock::now() + ms(4)) == true )// 每多等1ms,就会多一个fiber获取锁
            {
                std::cout << "get it!\n";
                FiberSleepFor(1 * 1ms);
                m.unlock();
            }
            else
            {
                std::cout << "timeout!\n";
            };
        };
        
        std::vector<Fiber> v;
        for(int i=0;i<9;i++){
            v.push_back(Fiber(Fiber::Attributes{.launch_policy = fiber::Launch::Post},func));
        }

        std::vector<Fiber>::iterator it;
        for(it=v.begin();it!=v.end();it++)
        {
            it->Join();
        }
    });
}


}  // namespace trpc
// ldconfig -p | grep test 
// g++  -std=c++11 test5.cpp -lboost_fiber -lboost_context -lboost_unit_test_framework  -o  test5

记录从0到1的过程:

Fiber

【腾讯文档】限流
https://docs.qq.com/doc/DQ3VNRHpPZnR3QUZK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值