MST

星途 面试题库

面试题:并发与同步之多线程同步机制对比

在操作系统多线程同步场景中,常见的同步机制有互斥锁、信号量、条件变量。请详细阐述这三种同步机制的特点、适用场景,并举例说明在哪些情况下应优先选择其中某一种机制。
30.3万 热度难度
操作系统并发与同步

知识考点

AI 面试

面试题答案

一键面试

互斥锁

  1. 特点
    • 一次只允许一个线程进入临界区,用于保证同一时间只有一个线程访问共享资源。
    • 它只有两种状态:锁定和解锁。
    • 互斥锁是一种特殊的二元信号量(值为0或1)。
  2. 适用场景
    • 适用于控制对共享资源的独占访问,当共享资源不允许多个线程同时访问时使用。例如,对文件的写操作,同一时间只应允许一个线程写入,避免数据混乱。
  3. 优先选择场景举例
    • 假设一个银行账户类,有存款和取款操作。账户余额是共享资源,为了保证在存款或取款时余额数据的一致性,应优先使用互斥锁。比如在Python中使用threading.Lock
import threading

class BankAccount:
    def __init__(self):
        self.balance = 0
        self.lock = threading.Lock()

    def deposit(self, amount):
        self.lock.acquire()
        try:
            self.balance += amount
        finally:
            self.lock.release()

    def withdraw(self, amount):
        self.lock.acquire()
        try:
            if self.balance >= amount:
                self.balance -= amount
        finally:
            self.lock.release()

信号量

  1. 特点
    • 信号量通过一个计数器来控制访问共享资源的线程数量。
    • 计数器的值表示当前可用的资源数量,线程获取信号量时,计数器减1;释放信号量时,计数器加1。
    • 当计数器为0时,获取信号量的线程会被阻塞。
  2. 适用场景
    • 适用于控制同时访问共享资源的线程数量。例如,数据库连接池,池中有固定数量的数据库连接,通过信号量来控制同时使用连接的线程数量,避免过多线程竞争连接资源。
  3. 优先选择场景举例
    • 假设有一个网络爬虫程序,要限制同时发起的HTTP请求数量,避免对目标服务器造成过大压力。在Python中可以使用threading.Semaphore
import threading
import requests

semaphore = threading.Semaphore(5)  # 允许同时5个请求

def crawl(url):
    semaphore.acquire()
    try:
        response = requests.get(url)
        print(f"爬取 {url} 成功")
    finally:
        semaphore.release()


urls = ["http://example.com", "http://example1.com", "http://example2.com"]
threads = []
for url in urls:
    t = threading.Thread(target=crawl, args=(url,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

条件变量

  1. 特点
    • 条件变量通常和互斥锁一起使用。
    • 它允许线程等待某个条件满足后再继续执行。线程可以在条件变量上等待,其他线程通过改变条件并通知条件变量,使等待的线程被唤醒。
  2. 适用场景
    • 适用于线程之间需要基于某种条件进行同步的场景。例如,生产者 - 消费者模型中,消费者线程需要等待生产者线程生产出数据后才能消费。
  3. 优先选择场景举例
    • 以生产者 - 消费者模型为例,在Python中使用threading.Condition
import threading

class Queue:
    def __init__(self):
        self.items = []
        self.lock = threading.Lock()
        self.condition = threading.Condition(self.lock)

    def enqueue(self, item):
        self.lock.acquire()
        try:
            self.items.append(item)
            self.condition.notify()
        finally:
            self.lock.release()

    def dequeue(self):
        self.lock.acquire()
        try:
            while not self.items:
                self.condition.wait()
            return self.items.pop(0)
        finally:
            self.lock.release()


queue = Queue()

def producer():
    for i in range(5):
        queue.enqueue(i)
        print(f"生产 {i}")


def consumer():
    for _ in range(5):
        item = queue.dequeue()
        print(f"消费 {item}")


producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

producer_thread.start()
consumer_thread.start()

producer_thread.join()
consumer_thread.join()