阻塞和非阻塞

在介绍阻塞和非阻塞之前,我们先来了解一下多线程间一个重要的概念——临界区。

临界区——一种公有的资源或者共享数据,它可以被多个线程使用。临界区资源一次只能被一个线程使用,其它线程必须等待上一个线程
执行完成之后,才能使用。临界区资源是多线程之间重要保护对象,当临界区资源同时被多个线程访问时,容易出现错误。
代码示例:
 
 
/**
 * @author php
 * @date 2018/7/2
 */
public class CriticalRegion {
    //定义1000元
    public static Integer money = 1000;

    public static void main(String[] args) throws InterruptedException {
        //定义公共资源
        CriticalRegion criticalRegion = new CriticalRegion();
        //定义10个线程来购物
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                criticalRegion.shop();
            });
        }
        Thread.sleep(1000);
        System.out.println("最终还有:" + CriticalRegion.money);
        executorService.shutdown();
    }

    //我们每次购物使用100块钱
    public  void shop() {
        money = money - 100;
        System.out.println("使用了100元,我还有:" + CriticalRegion.money);
    }
}

执行结果:从中可以看出,多个线程,同时操作临界区资源时,容易导致操作资源错误。在大部分情况下,这是一个很严重的问题。
大家可以多执行几次,看看结果有什么不同。


现在,我们修改一下shop()方法,在方法上加上sychronized。执行结果:sychronized可以让临界区资源,一次只能被一个线程访问,

从而结果正确。


临界区资源,在使用多线程时,要格外的注意,以免结果千奇百怪。

在我们了解临界区之后,再来了解阻塞和非阻塞的概念。

阻塞和非阻塞通常被用来形容多线程间的相互影响。当一个线程占用了临界区资源,那么其它需要使用这个资源的线程都必须在这个临界区上等待。等待会导致线程挂起,这样就形成了阻塞。如果占用资源的线程一直没有释放资源,那么其它的线程在这个临界区上都不能继续工作。

相反,非阻塞表明多个线程之间的执行是不会相互影响的。

通常,我们使用synchronized关键字,ReentrantLock(重入锁)时,我们得到的线程就是阻塞线程。阻塞线程在执行代码前,都会尝试得到临界区资源的锁,如果得不到,线程就会一直挂起,直到临界区资源释放。
### Linux 阻塞非阻塞 I/O 模型区别 在 Linux 系统中,阻塞非阻塞 I/O 是两种不同的输入输出处理方式。这两种模式的主要差异在于进程在等待 I/O 操作完成期间的状态。 #### 阻塞 I/O (Blocking I/O) 当一个进程发出阻塞 I/O 请求时,在该请求未得到响应之前,进程会被挂起并进入等待状态,直到数据准备就绪操作完成为止[^1]。这意味着在此期间,CPU 不会分配时间给这个被挂起的进程,从而可以释放 CPU 资源用于其他任务。 对于阻塞 I/O 的实现,通常采用 `wait_queue` 来管理多个可能需要唤醒的任务列表。一旦有新的事件发生(比如设备准备好读取的数据),内核就会遍历相应的等待队列来激活这些休眠中的线程进程。 ```c // C code example of blocking read using wait queue in a device driver context. static ssize_t my_device_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { DECLARE_WAIT_QUEUE_HEAD(wq); // Add current process to the waiting list and sleep until data is available or interrupted by signal. add_wait_queue(&wq, &wait); while (!data_available()) { // Check if there's any data ready for reading set_current_state(TASK_INTERRUPTIBLE); schedule(); // Put this thread into sleeping state if (signal_pending(current)) { remove_wait_queue(&wq, &wait); return -ERESTARTSYS; } } // Data processing logic here... remove_wait_queue(&wq, &wait); } ``` #### 非阻塞 I/O (Non-blocking I/O) 相比之下,非阻塞 I/O 并不会让调用它的进程陷入长时间的等待之中。相反,如果当前无法立即满足 I/O 请求,则函数会立刻返回错误码告知用户空间程序此时不可行的操作,并允许应用程序继续执行其他的逻辑而不必停留在原地不动[^2]。为了有效地管理监控多个文件描述符上的活动情况,Linux 提供了像 `select()` `poll()` 这样的系统调用来支持多路复用机制[^4]。 ```python import select import socket sock = socket.socket() sock.setblocking(False) # Set non-blocking mode on the socket. try: sock.connect(('example.com', 80)) except BlockingIOError as e: pass # Connection attempt has been initiated but not yet established. ready_to_write, _, _ = select.select([], [sock], [], timeout=5) if ready_to_write: print("Socket can now send/receive data.") else: print("Connection timed out without establishing connection.") ``` ### 应用场景分析 - **Web服务器**:由于 Web 服务端往往要同时处理大量并发连接请求,因此非常适合使用基于 `epoll` 或者更传统的 `select/poll` 实现的非阻塞 I/O 多路复用技术来提高效率服务质量。 - **嵌入式开发**:在资源受限环境下工作的硬件控制系统可能会倾向于选择简单的阻塞 I/O 方案以简化编程复杂度;然而随着实时性性能需求的增长,也会逐渐转向更为灵活高效的非阻塞设计思路。 - **数据管理系统**:这类软件产品内部涉及复杂的磁盘访问操作,它们既可以通过配置参数调整默认行为为阻塞还是非阻塞形式,也可以利用高级特性如异步提交日志等手段进一步优化整体吞吐量表现。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值