对于函数的递归调用,大家都比较熟悉了,但有些情况下需要防止函数的递归调用,该怎么实现呢?同一线程内,只需要借助一个全局变量即可。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct RecursionGuard
{
RecursionGuard() : wasLocked(isActive) {
isActive = true;
}
~RecursionGuard() {
isActive = wasLocked;
}
const bool wasLocked;
static thread_local bool isActive;
};
thread_local bool RecursionGuard::isActive = false;
void g() {
std::cout << "enter g" << std::endl;
if (RecursionGuard::isActive) {
std::cout << "failed to acquire permission" << std::endl;
return;
}
RecursionGuard guard;
g(); // 嵌套调用
std::cout << "exit g" << std::endl;
}
int main() {
g();
return 0;
}
上述代码中,函数 g 里嵌套调用了 g,但是由于全局的递归保护锁在当前函数中,嵌套函数 g 里拿不到锁。输出结果如下:
与函数的无穷递归调用类似,还有一种场景是定时器导致进程陷入软中断中出不来:
int var = 0;
void f(int signo) {
std::cout << "kernel thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(12));
var += 10;
std::cout << "var in kernel thread: " << var << std::endl;
}
int main() {
struct sigaction sa;
sa.sa_handler = f;
if (sigaction(SIGALRM, &sa, nullptr) < 0) {
std::cout << "sigaction error\n";
}
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 0;
tick.it_value.tv_usec = 10 * 1000;
tick.it_interval.tv_sec = 0;
tick.it_interval.tv_usec = 10 * 1000;
setitimer(ITIMER_REAL, &tick, nullptr);
std::cout << "main thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
for (int i = 0; i < 5; i++) {
var += 1;
std::cout << "var in main thread: " << var << std::endl;
}
return 0;
}
上述代码 main 函数中启动了一个 10ms 的定时器,定时器触发的软中断 handler 是函数 f,f 中再次休眠 12ms,于是,问题来了:前一个 SIGALRM 信号触发的函数 f 还没执行完,下一个定时器信号又到了,所以进程卡死在 handler 里,死循环一直出不来了。
程序输出结果如下:
答案是不能。对于系统调用 sigaction 而言,定时器触发的信号会排队等待处理,也就是说 handler 是阻塞的。而 signal 则是会丢弃定时器触发的信号。
void f(int signo) {
std::cout << "enter f" << std::endl;
if (RecursionGuard::isActive) {
return;
}
RecursionGuard guard;
std::cout << "kernel thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(12));
var += 10;
std::cout << "var in kernel thread: " << var << std::endl;
}
先看下述代码:
// f1 为主线程函数
void f1() {
SpinLock(&lock);
/* 被保护临界区 */
spinUnlock(&lock);
}
// f2 设置为定时器的 handler
void f2() {
SpinLock(&lock);
/* 被保护临界区 */
SpinLock(&lock);
}
解释下:
(1) f1 在进程上下文执行
时钟中断到,执行 f2,此时拿锁是拿不到的。因为 f1 处于进程上下文,此时 CPU 已被 f2 抢占,f1 始终无法释放锁,此时发生死锁。
(2) f1 在中断上半部执行(硬中断)
时钟中断到,因为 timer 的中断执行函数在软中断中执行,属于下半部上下文,不能抢占上半部,所以不会发生死锁。
(3) f1 在中断下半部执行(软中断)
时钟中断到,因为 f2 和 f1 同为下半部执行,所以不会抢占 f1,不会死锁。注意,这种情况下也只能使用自旋锁,因为互斥锁会休眠的。
\2. 如果 f2 先拿锁,也有以下三种情况:
(1) f1 在进程上下文执行
因为 f2 在中断上下文,不会被 f1 抢,所以不会发生死锁。
(2) f1 在中断上半部执行(硬中断)
因为 f1 之前没有关中断,f2 在软中断中执行,硬中断可以打断软中断,f2 未释放锁,f1 拿不到锁,发生死锁。
(3) f1 在中断下半部执行(软中断)
时钟中断到,因为 f2 和 f1 同为下半部执行,所以不会抢占 f2,不会死锁。
总结下:中断处理函数只有被执行,才有机会去拿锁,如果连被执行的机会都没有,是不会有抢别人锁的机会的。
下面是在软中断中使用多线程处理的一个示例:
#include
int var = 0;
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void h() {
while (lock.test_and_set(std::memory_order_acquire)) {} //获得自旋锁
var += 10;
std::cout << "var in kernel thread " << std::this_thread::get_id() << ": " << var << std::endl;
lock.clear(std::memory_order_release); //释放自旋锁
}
void f(int signo) {
std::thread t1(h);
std::thread t2(h);
t1.join();
t2.join();
}
int main() {
std::cout << "main thread: " << std::this_thread::get_id() << std::endl;
struct sigaction sa;
sa.sa_handler = f;
if (sigaction(SIGALRM, &sa, nullptr) < 0) {
std::cout << "sigaction error\n";
}
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 0;
tick.it_value.tv_usec = 1 * 1000;
tick.it_interval.tv_sec = 0;
tick.it_interval.tv_usec = 2 * 1000;
setitimer(ITIMER_REAL, &tick, nullptr);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return 0;
}
程序输出结果如下:
注意:在多个子线程力启多个定时器是没有效果的。
触发中断时,需要用到栈。 中断 A 正在处理的过程中,假设又发生了中断 B,那么在栈里要保存 A 的现场,然后处理 B。在处理 B 的过程中又发生了中断 C,那么在栈里要保存B的现场,然后处理 C。 如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。所以,为了防止这种情况发生,也是为了简单化中断的处理,在 Linux 系统上中断无法嵌套:即当前中断 A 没处理完之前,不会响应另一个中断 B(即使它的优先级更高)。
代码 1:
class SpinMutex {
public:
SpinMutex() = default;
SpinMutex(const SpinMutex&) = delete;
SpinMutex& operator=(const SpinMutex&) = delete;
void lock() {
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
int var = 0;
SpinMutex spin_mtx;
void f() {
// std::lock_guard lock(spin_mtx);
var += 1;
std::cout << "var in kernel thread " << std::this_thread::get_id() << ": " << var << std::endl;
}
void g() {
// std::lock_guard lock(spin_mtx);
var += 1;
std::cout << "var in main thread " << std::this_thread::get_id() << ": " << var << std::endl;
}
void h(int signo) {
f();
}
int main() {
struct sigaction sa;
sa.sa_handler = h;
if (sigaction(SIGALRM, &sa, nullptr) < 0) {
std::cout << "sigaction error\n";
}
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 0;
tick.it_value.tv_usec = 1 * 1000;
tick.it_interval.tv_sec = 0;
tick.it_interval.tv_usec = 1 * 1000;
setitimer(ITIMER_REAL, &tick, nullptr);
//std::this_thread::sleep_for(std::chrono::milliseconds(10));
for (int i = 0; i < 100000; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
g();
}
return 0;
}
很明显,内核软中断和用户态线程都操作了全局变量 var,var 没有得到保护。
代码 2:
int var = 0;
SpinMutex spin_mtx;
void f() {
std::lock_guard<SpinMutex> lock(spin_mtx);
var += 1;
std::cout << "var in kernel thread " << std::this_thread::get_id() << ": " << var << std::endl;
}
void g() {
std::lock_guard<SpinMutex> lock(spin_mtx);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
var += 1;
std::cout << "var in main thread " << std::this_thread::get_id() << ": " << var << std::endl;
}
void h(int signo) {
f();
}
int main() {
struct sigaction sa;
sa.sa_handler = h;
if (sigaction(SIGALRM, &sa, nullptr) < 0) {
std::cout << "sigaction error\n";
}
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 0;
tick.it_value.tv_usec = 1 * 1000;
tick.it_interval.tv_sec = 0;
tick.it_interval.tv_usec = 1 * 1000;
setitimer(ITIMER_REAL, &tick, nullptr);
for (int i = 0; i < 100000; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
g();
}
return 0;
}
虽说代码 2 中加了自旋锁,但是分别在内核软中断和用户态线程中,还是会造成进程卡死。有没办法解决该问题呢?借助一个标记 flag 就可以,请看代码 3:
代码 3:
int var = 0;
int flag = 0;
void f() {
if (flag == 1) {
return;
}
var += 1;
std::cout << "var in kernel thread " << std::this_thread::get_id() << ": " << var << std::endl;
}
void g() {
flag = 1;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
var += 1;
std::cout << "var in main thread " << std::this_thread::get_id() << ": " << var << std::endl;
flag = 0;
}
void h(int signo) {
f();
}
int main() {
struct sigaction sa;
sa.sa_handler = h;
if (sigaction(SIGALRM, &sa, nullptr) < 0) {
std::cout << "sigaction error\n";
}
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 0;
tick.it_value.tv_usec = 1 * 1000;
tick.it_interval.tv_sec = 0;
tick.it_interval.tv_usec = 1 * 1000;
setitimer(ITIMER_REAL, &tick, nullptr);
for (int i = 0; i < 100000; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
g();
}
return 0;
}
更多细节及代码示例,持续更新中。。。