问题

这个东西的探讨要从前天在b 站看到一个有趣的问题开始, 它启发了我对多进程与多线程的思考.

这个问题是: 有 2 个线程, 一个线程打印一个A, 一个线程打印B, 问如何让这两个线程交替打印 100 次?

线程

线程是操作系统能够进行运算调度的最小单位. 它被包含在进程之中, 是进程中的实际运作单位. 我第一时间看到了,用 condition 来处理线程的切换以及同步问题

C

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <pthread.h>
#include <stdio.h>

#define NUM_ITERATIONS 100

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int print_a = 1;

void *print_a_thread(void *arg) {
  for (int i = 0; i < NUM_ITERATIONS; ++i) {
    pthread_mutex_lock(&mutex);
    while (!print_a) {
      pthread_cond_wait(&cond, &mutex);
    }
    printf("A\n");
    print_a = 0;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

void *print_b_thread(void *arg) {
  for (int i = 0; i < NUM_ITERATIONS; ++i) {
    pthread_mutex_lock(&mutex);
    while (print_a) {
      pthread_cond_wait(&cond, &mutex);
    }
    printf("B\n");
    print_a = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

int main() {
  pthread_t thread_a, thread_b;

  // Create the threads
  pthread_create(&thread_a, NULL, print_a_thread, NULL);
  pthread_create(&thread_b, NULL, print_b_thread, NULL);

  // Wait for the threads to finish
  pthread_join(thread_a, NULL);
  pthread_join(thread_b, NULL);

  // Clean up
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);

  return 0;
}

Python

我就在思考, 这个问题如果用python来解决, 会是怎样的呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import threading

NUM_ITERATIONS = 100

# Create a lock and an event
event_a = threading.Event()
event_a.set()
# Set the event to True, so that the first thread can start printing 'A'

event_b = threading.Event()
def print_a_thread():
    for _ in range(NUM_ITERATIONS):
            # Wait for the event to be set (which means it's 'A's turn)
            event_a.wait()
            print("A")
            # Clear the event to signal that it's not 'A's turn anymore
            event_a.clear()
            # Set the event for 'B' to start printing
            event_b.set()

def print_b_thread():
    for _ in range(NUM_ITERATIONS):
            # Wait for the event to be cleared (which means it's 'B's turn)
            event_b.wait()
            print("B")
            # Set the event to signal that it's not 'B's turn anymore
            event_b.clear()
            # Set the event for 'A' to start printing
            event_a.set()

# Create the threads
thread_a = threading.Thread(target=print_a_thread)
thread_b = threading.Thread(target=print_b_thread)

# Start the threads
thread_a.start()
thread_b.start()

# Wait for the threads to finish
thread_a.join()
thread_b.join()

Golang

因为好奇,对协程友好的golang会怎么解决这个问题呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
	"fmt"
	"sync"
)

const NUM_ITERATIONS = 100

func printA(wg *sync.WaitGroup, chA, chB chan struct{}) {
	defer wg.Done()
	for i := 0; i < NUM_ITERATIONS; i++ {
		<-chA
		fmt.Println("A")
		chB <-struct{}{} // Wait for the other goroutine to print 'B'
	}
}

func printB(wg *sync.WaitGroup, chA,chB chan struct{}) {
	defer wg.Done()
	for i := 0; i < NUM_ITERATIONS; i++ {
		<-chB // Wait for the other goroutine to print 'A'
		fmt.Println("B")
		if i < NUM_ITERATIONS-1 {
			chA<-struct{}{} // Signal the other goroutine to print 'A'
		}
	}
}

func main() {
	var wg sync.WaitGroup
	chA := make(chan struct{},1)
	chB := make(chan struct{})

	wg.Add(2)
	go printA(&wg, chA, chB)
	go printB(&wg, chA, chB)
	chA <- struct{}{} // Start the first goroutine

	wg.Wait() // Wait for both goroutines to finish
	close(chA)
	close(chB)
}

Golang 是真的很简洁了, 用 channel 来解决这个问题,太简洁了

进程

进程是操作系统资源分配的最小单位, 它是程序的一次执行过程, 是一个动态的概念, 是程序在一个数据集合上的一次运行活动. 我最近看jyy的操作系统课程,我又重新理解了fork/execve/_wait 等系统调用, 以及进程间通信的方式.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#include <unistd.h>

int main(){

  for (int i = 0; i < 3; i++){
    fork();
    printf("Hello");
    printf("\n");
    //fflush(stdout);
  }
}

如何理解这个进程会打印14Hello呢?

$ 2 + 4+ 8 = 14 $

fork 是一个二叉树的过程, 一个进程 fork 出来的子进程, 会继续 fork 出来一个子进程, 以此类推, 所以会有 14 个Hello. 记住它是一个状态机的复制

Fork Bomb

你知道什么是 fork 炸弹吗? 一个简单的 shell 脚本就可以让你的系统崩溃

1
:(){ :|:& };:

上面的命令等同于

1
2
3
bomb() {
  bomb | bomb &
}

总结

如果我想知道更多,我觉得我未来需要去了解硬件实现,需要知道每一个原子操作是怎么实现的,这样我才能更好的理解操作系统,编程语言,编译器等等.