linux多线程时序问题,Linux时序竞态问题(sleep函数的实现)

时序竞态是指同样的程序,多次调用运行的结果不同,这是由于争夺系统资源所造成的。比如说我们要使用alarm和pause函数来实现一个sleep的功能,那么由于alarm函数的实现过程并不是一个原子操作,那么随时可能被中断。比如说alarm了1秒,在这个过程中,进程失去了CPU,然后当该进程再次获得CPU的时候可能这个时间已经大于1秒了,那么对于alarm来说就已经发出了SIGALRM信号。此时往下继续调用pause函数的话,它会一直都收不到alarm发来的信号,所以导致进程的永久挂起。

为了解决这个问题,引用了sigsuspend函数。sigsuspend用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。也就是说,进程执行到sigsuspend时,sigsuspend并不会立刻返回,进程处于TASK_INTERRUPTIBLE状态并立刻放弃CPU,等待UNBLOCK(mask之外的)信号的唤醒。进程在接收到UNBLOCK(mask之外)信号后,调用处理函数,然后还原信号集,sigsuspend返回,进程恢复执行。下面通过使用alarm和sigsuspend函数来实现sleep函数,代码中有详细的注释来解释说明:

#include

#include

#include

#include

#include

#include

void sig_alarm(int num){ // 空函数仅用来捕捉信号

}

unsigned int mysleep(unsigned int n_seconds){

struct sigaction n_sig, o_sig; // 一个用来设定,一个用来备份

sigset_t nsigmask, osigmask, tmpsigmask; // 用来设定,备份,替换

/*初始化*/

n_sig.sa_handler = sig_alarm; // 设定信号的捕捉动作

n_sig.sa_flags = 0;

sigemptyset(&n_sig.sa_mask);

sigaction(SIGALRM, &n_sig, &o_sig); // 设定SIGALRM的处理行为

/*对SIGALRM信号设置阻塞,防止在挂起前出现递达态*/

sigemptyset(&nsigmask);

sigaddset(&nsigmask, SIGALRM);

sigprocmask(SIG_BLOCK, &nsigmask, &osigmask); // 设置新mask,备份旧mask

alarm(n_seconds); // 设置n秒的闹钟

/*对原有的mask取消SIGALRM的阻塞*/

tmpsigmask = osigmask;

sigdelset(&tmpsigmask, SIGALRM);

sigsuspend(&tmpsigmask); // 挂起等待没有被阻塞的信号

sigprocmask(SIG_SETMASK, &osigmask, NULL); // 还原mask

sigaction(SIGALRM, &o_sig, NULL); // 还原处理行为

unsigned int unsletp = alarm(0); // 获取剩余的睡眠时间

/*因为如果没有睡眠够n_seconds就收到了一个唤醒信号,那么再次调用alarm会返回剩余的时间*/

return unsletp;

}

int main(void)

{

int ans = mysleep(5);

printf("%d\n", ans);

return 0;

}

本文同步分享在 博客“Ch_zaqdt”(优快云)。

如有侵权,请联系 support@oschina.cn 删除。

本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

### 多线程环境下的时序不确定性 在多线程环境中,无法确保执行顺序的主要原因在于硬件架构和操作系统调度机制的设计。现代计算机系统通常配备有多核CPU,这允许多个线程真正意义上并行运行[^1]。 当应用程序启动多个线程并发工作时,这些线程的具体执行时间片分配完全取决于操作系统的调度算法。不同线程可能在同一时刻争相同的资源(如内存地址),如果没有适当的同步措施,则可能导致条件的发生。例如,在`ThreadRoutine`函数中对共享变量`tickets`的操作缺乏必要的保护机制,使得两个或更多线程能够几乎同时尝试更改同一份数据,进而引发不可预知的行为变化。 此外,为了提高性能,处理器内部会实施各种形式的指令重排序以及缓存策略,而编程语言层面也可能存在编译器优化带来的语句重新排列现象。这意味着即使是在同一个线程内的操作序列也未必严格按照源码书写的次序被执行完毕后再轮到其他线程继续。对于未经过良好同步设计的应用来说,这种行为打破了程序员预期中的因果关系链路,造成了所谓的“非顺序一致性”的效果[^3]。 最后值得注意的是,即便采取了某些手段试图强制特定线程间的相对顺序(比如通过CountDownLatch等方式串连起一系列依赖性的任务),也无法绝对排除外部因素干扰所造成的意外情况发生。因此,在开发涉及高并发场景的应用程序时,应当充分考虑到上述各方面的影响,并采用合适的技术方案来保障业务逻辑的一致性和可靠性。 ```java // Java示例:展示如何创建一个简单的线程池并通过submit提交异步任务 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AsyncExample { public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); Runnable taskA = () -> System.out.println("Task A starts"); Runnable taskB = () -> System.out.println("Task B starts"); // 提交的任务不一定按照提交顺序立即执行 executor.submit(taskA); Thread.sleep(1); // 尝试引入轻微延迟影响实际执行时机 executor.submit(taskB); executor.shutdown(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值