题目

1114. 按序打印

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例,并分别会调用其中的一个方法,每个调用方法不同。

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

class Foo:
    def __init__(self):
        self.l2 = threading.Lock()
        self.l3 = threading.Lock()
        self.l2.acquire()
        self.l3.acquire()
 
    def first(self, printFirst: 'Callable[[], None]') -> None:
        printFirst()
        self.l2.release()
 
    def second(self, printSecond: 'Callable[[], None]') -> None:
        self.l2.acquire()
        printSecond()
        self.l3.release()
 
    def third(self, printThird: 'Callable[[], None]') -> None:
        self.l3.acquire()
        printThird()
from threading import Semaphore
 
class Foo:
    def __init__(self):
        self.a = Semaphore(1)
        self.b = Semaphore(0)
        self.c = Semaphore(0)
 
    def first(self, printFirst: 'Callable[[], None]') -> None:
        self.a.acquire()
        # printFirst() outputs "first". Do not change or remove this line.
        printFirst()
        self.b.release()
 
    def second(self, printSecond: 'Callable[[], None]') -> None:
        self.b.acquire()
        # printSecond() outputs "second". Do not change or remove this line.
        printSecond()
        self.c.release()
 
    def third(self, printThird: 'Callable[[], None]') -> None:
        self.c.acquire()
        # printThird() outputs "third". Do not change or remove this line.
        printThird()
        self.a.release()

1115. 交替打印 FooBar

给你一个类,两个线程公用一个 FooBar 类,一个调用 foo,一个调用 bar。修改该类,使得 Foo 与 Bar 交替打印 n 次。

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }
 
  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}
from threading import Semaphore
 
class FooBar:
    def __init__(self, n):
        self.n = n
        self.f = Semaphore(1)
        self.b = Semaphore(0)
 
    def foo(self, printFoo: "Callable[[], None]") -> None:
        for _ in range(self.n):
            self.f.acquire()
            # printFoo() outputs "foo". Do not change or remove this line.
            printFoo()
            self.b.release()
 
    def bar(self, printBar: "Callable[[], None]") -> None:
        for _ in range(self.n):
            self.b.acquire()
            # printBar() outputs "bar". Do not change or remove this line.
            printBar()
            self.f.release()

1116. 打印零与奇偶数

实现 ZeroEvenOdd 类:

  • ZeroEvenOdd(int n) 用数字 n 初始化对象,表示需要输出的数。
  • void zero(printNumber) 调用 printNumber 以输出一个 0 。
  • void even(printNumber) 调用 printNumber 以输出偶数。
  • void odd(printNumber) 调用 printNumber 以输出奇数。

该类中有三个函数:zeroeven 和 odd 。ZeroEvenOdd 的相同实例将会传递给三个不同线程:

  • 线程 A:调用 zero() ,只输出 0
  • 线程 B:调用 even() ,只输出偶数
  • 线程 C:调用 odd() ,只输出奇数

修改给出的类,以输出序列 "010203040506..." ,其中序列的长度必须为 2n 。

from threading import Semaphore
 
class ZeroEvenOdd:
    def __init__(self, n):
        self.n = n
        self.z = Semaphore(1)
        self.e = Semaphore(0)
        self.o = Semaphore(0)
 
    # printNumber(x) outputs "x", where x is an integer.
    def zero(self, printNumber: 'Callable[[int], None]') -> None:
        for i in range(self.n):
            self.z.acquire()
            printNumber(0)
            if i % 2 == 0:
                self.o.release()
            else:
                self.e.release()
 
    def even(self, printNumber: 'Callable[[int], None]') -> None:
        for i in range(2, self.n + 1, 2):
            self.e.acquire()
            printNumber(i)
            self.z.release()
 
    def odd(self, printNumber: 'Callable[[int], None]') -> None:
        for i in range(1, self.n + 1, 2):
            self.o.acquire()
            printNumber(i)
            self.z.release()

1117. H2O 生成

现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。

存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。

氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。

这些线程应该三三成组突破屏障并能立即组合产生一个水分子。

你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。

换句话说:

  • 如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
  • 如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。

书写满足这些限制条件的氢、氧线程同步代码。

from threading import Semaphore
 
# 这里应该用 Barrier 更合适,Barrier 设置为 2,
# hydrogen 中调用两次 releaseHydrogen 后,进行 wait
# oxygen 中调用一次 releaseOxygen 后,进行 wait
 
class H2O:
    def __init__(self):
        self.h = Semaphore(2)
        self.o = Semaphore(0)
 
    def hydrogen(self, releaseHydrogen: "Callable[[], None]") -> None:
        self.h.acquire()
        # releaseHydrogen() outputs "H". Do not change or remove this line.
        releaseHydrogen()
        if self.h._value == 0:
            self.o.release()
 
    def oxygen(self, releaseOxygen: "Callable[[], None]") -> None:
        self.o.acquire()
        # releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen()
        self.h.release(2)

1188. 设计有限阻塞队列

实现一个拥有如下方法的线程安全有限阻塞队列:

  • BoundedBlockingQueue(int capacity) 构造方法初始化队列,其中 capacity 代表队列长度上限。
  • void enqueue(int element) 在队首增加一个 element. 如果队列满,调用线程被阻塞直到队列非满。
  • int dequeue() 返回队尾元素并从队列中将其删除. 如果队列为空,调用线程被阻塞直到队列非空。
  • int size() 返回当前队列元素个数。

你的实现将会被多线程同时访问进行测试。每一个线程要么是一个只调用 enqueue 方法的生产者线程,要么是一个只调用 dequeue 方法的消费者线程。size 方法将会在每一个测试用例之后进行调用。

from threading import Semaphore
 
class BoundedBlockingQueue(object):
    def __init__(self, capacity: int):
        self.s1 = Semaphore(capacity)
        self.s2 = Semaphore(0)
        self.q = deque()
 
    def enqueue(self, element: int) -> None:
        self.s1.acquire()
        self.q.append(element)
        self.s2.release()
 
    def dequeue(self) -> int:
        self.s2.acquire()
        ans = self.q.popleft()
        self.s1.release()
        return ans
 
    def size(self) -> int:
        return len(self.q)

1195. 交替打印字符串

编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:

  • 如果这个数字可以被 3 整除,输出 “fizz”。
  • 如果这个数字可以被 5 整除,输出 “buzz”。
  • 如果这个数字可以同时被 3 和 5 整除,输出 “fizzbuzz”。

例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz

假设有这么一个类:

class FizzBuzz {
  public FizzBuzz(int n) { … }  // constructor
public void fizz(printFizz) { … } // only output “fizz”
public void buzz(printBuzz) { … } // only output “buzz”
public void fizzbuzz(printFizzBuzz) { … } // only output “fizzbuzz”
public void number(printNumber) { … } // only output the numbers
}

请你实现一个有四个线程的多线程版  FizzBuzz,同一个 FizzBuzz 实例会被如下四个线程使用:

  1. 线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz
  2. 线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz
  3. 线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz
  4. 线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。
class FizzBuzz {
    private int n;
 
    public FizzBuzz(int n) {
        this.n = n;
    }
 
    private Semaphore fSema = new Semaphore(0);
    private Semaphore bSema = new Semaphore(0);
    private Semaphore fbSema = new Semaphore(0);
    private Semaphore nSema = new Semaphore(1);
 
    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        for (int i = 3; i <= n; i = i + 3) {
            if (i % 5 != 0) {
                fSema.acquire();
                printFizz.run();
                nSema.release();
            }
        }
    }
 
    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        for (int i = 5; i <= n; i = i + 5) {
            if (i % 3 != 0) {
                bSema.acquire();
                printBuzz.run();
                nSema.release();
            }
        }
    }
 
    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        for (int i = 15; i <= n; i = i + 15) {
            fbSema.acquire();
            printFizzBuzz.run();
            nSema.release();
        }
    }
 
    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i++) {
            nSema.acquire();
            if (i % 3 == 0 && i % 5 == 0) {
                fbSema.release();
            } else if (i % 3 == 0) {
                fSema.release();
            } else if (i % 5 == 0) {
                bSema.release();
            } else {
                printNumber.accept(i);
                nSema.release();
            }
        }
    }
}

1226. 哲学家进餐

哲学家从 0 到 4 按顺时针编号。请实现函数 void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)

  • philosopher 哲学家的编号。
  • pickLeftFork 和 pickRightFork 表示拿起左边或右边的叉子。
  • eat 表示吃面。
  • putLeftFork 和 putRightFork 表示放下左边或右边的叉子。
  • 由于哲学家不是在吃面就是在想着啥时候吃面,所以思考这个方法没有对应的回调。

给你 5 个线程,每个都代表一个哲学家,请你使用类的同一个对象来模拟这个过程。在最后一次调用结束之前,可能会为同一个哲学家多次调用该函数。

# 奇数的哲学家先拿左边叉子,偶数的先拿右边叉子
from threading import Lock
 
class DiningPhilosophers:
    def __init__(self):
        self.ForkLocks = [Lock() for _ in range(5)] # 叉子锁
 
    def wantsToEat(self,
                   philosopher: int,
                   pickLeftFork: 'Callable[[], None]',
                   pickRightFork: 'Callable[[], None]',
                   eat: 'Callable[[], None]',
                   putLeftFork: 'Callable[[], None]',
                   putRightFork: 'Callable[[], None]') -> None:
        # 左右叉子的编号
        right_fork = philosopher
        left_fork = (philosopher + 1) % 5
 
        # 偶数编号的先拿右边叉子
        if philosopher % 2 == 0:
            self.ForkLocks[right_fork].acquire()
            self.ForkLocks[left_fork].acquire()
            
        #奇数编号的先拿左边叉子
        else:
            self.ForkLocks[left_fork].acquire()
            self.ForkLocks[right_fork].acquire()
        pickLeftFork()
        pickRightFork()
        eat()
        putLeftFork()
        putRightFork()
        self.ForkLocks[right_fork].release()
        self.ForkLocks[left_fork].release()

Python 有序执行方法

Lock

Lock(锁):借助 threading.Lock 类实现,保证同一时刻只有一个线程能访问共享资源,避免数据竞争。像多个线程对同一变量操作时,就可使用它。

import threading
 
lock = threading.Lock()
lock.release() # 生产者
lock.acquire() # 消费者

RLock

RLock(可重入锁):threading.RLock 类允许同一线程多次获取锁,适用于递归调用场景。

import threading
 
lock = threading.RLock()
lock.release() # 生产者
lock.acquire() # 消费者

Semaphore

Semaphore(信号量):threading.Semaphore 类能限制同时访问资源的线程数量。例如,限制同时访问数据库的线程数量。

import threading
 
semaphore = threading.Semaphore(10)
semaphore.release() # 生产者,默认生产一个,可以 release(n)
semaphore.acquire() # 消费者,默认消费一个,可以 acquire(n),不够 n 则阻塞

Condition

Condition(条件变量):threading.Condition 类结合了锁与信号机制,让线程在特定条件下等待或被唤醒,常用于生产者 - 消费者模型。

Condition 还支持 waitnotify 功能。Condition 的 wait() 造成的等待状态与普通的阻塞(如 time.sleep())不同。time.sleep() 是线程主动暂停执行一段时间,期间不会释放锁;而 wait() 是在特定条件下等待,会释放锁并等待其他线程的通知。

import threading
 
condition = threading.Condition()
shared_resource = None
 
def producer():
    global shared_resource
    with condition:
        shared_resource = "资源已生产"
        print("生产者生产了资源")
        condition.notify()
 
def consumer():
    with condition:
        while shared_resource is None:
            print("消费者等待资源")
            condition.wait()
        print("消费者获取到资源:", shared_resource)

Event

Event(事件):threading.Event 类可用于线程间的简单通信,一个线程能设置事件,其他线程等待该事件被设置。

import threading
import time
 
event = threading.Event()
 
def waiter():
    print("等待事件被设置...")
    event.wait()
    print("事件已被设置,继续执行")
 
waiter_thread = threading.Thread(target=waiter)
waiter_thread.start()
 
time.sleep(2)
event.set()
 
waiter_thread.join()

event 还有 clear() 方法,可以清除事件,以便下次等待。

Barrier

Barrier(屏障):threading.Barrier 类可使一组线程在某点同步,所有线程都到达该点后才能继续执行。

也和 Java 中的 Barrier 相同,当所有线程都到达该点后,可以自动重置屏障。

import threading
import time
 
barrier = threading.Barrier(3)
 
def worker(id):
    for round_num in range(3): 
        print(f"线程 {id} 到达第 {round_num + 1} 轮屏障")
        try:
            # 等待其他线程到达屏障
            barrier.wait()
            # 都到达屏障后,开始执行任务,如百米赛跑等待所有人准备就绪
            print(f"线程 {id} 开始第 {round_num + 1} 轮工作")
            time.sleep(id * 10)
        except threading.BrokenBarrierError:
            print(f"线程 {id} 检测到第 {round_num + 1} 轮屏障已损坏")
        print(f"线程 {id}{round_num + 1} 轮完成")
 
threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()
 
for t in threads:
    t.join()