1. 线程与进程
1.1 线程
线程和进程在操作系统中的表现形式与应用场景有所不同。进程是操作系统中资源分配的基本单位,而线程是进程中的实际执行单元。
特性 |
进程 |
线程 |
资源独立性 |
各进程有独立的地址空间 |
线程共享进程的地址空间和资源 |
执行单元 |
每个进程一个程序计数器 |
每个线程有自己的程序计数器 |
调度单位 |
操作系统调度的基本单位 |
进程内的调度由线程完成 |
创建和销毁复杂度 |
创建开销较大,资源隔离明显 |
较轻量,资源共享 |
上下文切换 |
开销高(完整切换) |
相对较低(部分切换) |
独立性与鲁棒性 |
较高,一个崩溃不影响其他进程 |
较低,一个线程崩溃可能使整个进程失败 |
线程由进程创建、管理,同时完成资源的共享。同一进程内的线程虽共享数据,但并不能避免各自独立的栈和控制信息。因此在多线程程序中,为了避免线程之间的冲突,必须实施适当的同步控制。
1.2 线程的同步问题
在多线程用户程序中常见的两种同步问题包括:
-
竞态条件:
- 由于线程可能在执行中被打断,因此多个线程可能同一时间访问共享数据,导致数据的不确定性和数据竞争。
-
线程依赖关系:
- 某些线程需要等待其他线程完成某些操作以达到特定的条件,这时需要通过同步机制来实现线程间的协调。
2. 线程的同步机制
2.1 互斥锁(Mutex)
互斥锁用于保证线程对共享数据的独占访问,避免竞态条件的发生。互斥锁通过锁定资源,保证临界区在任何时刻只能被一个线程访问。对于Pthreads库,常用的互斥锁函数包括:
- 初始化:
pthread_mutex_init()
用于初始化互斥锁,可以指定锁的属性。
- 加锁:
pthread_mutex_lock()
加锁,如果锁已经被占用,则线程进入睡眠。
- 尝试加锁:
pthread_mutex_trylock()
进行加锁尝试而不阻塞。
- 定时加锁:
pthread_mutex_timedlock()
提供超时机制进行加锁。
- 解锁:
pthread_mutex_unlock()
释放锁。
- 销毁:
pthread_mutex_destroy()
销毁锁,解除对资源的锁定。
使用C语言中的pthread库实现互斥锁的示例
以下示例演示了如何在多线程环境中使用pthread互斥锁保护共享数据。
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 55 56
| #include <stdio.h> #include <pthread.h> #include <unistd.h>
pthread_mutex_t mtx;
int shared_counter = 0;
void* incrementCounter(void* arg) { for (int i = 0; i < 5; ++i) { pthread_mutex_lock(&mtx);
printf("Thread %ld: Counter = %d\n", (long)pthread_self(), shared_counter); shared_counter++;
pthread_mutex_unlock(&mtx);
usleep(100000); } return NULL; }
int main() { pthread_mutex_init(&mtx, NULL);
const int numberOfThreads = 5; pthread_t threads[numberOfThreads];
for (int i = 0; i < numberOfThreads; ++i) { pthread_create(&threads[i], NULL, incrementCounter, NULL); }
for (int i = 0; i < numberOfThreads; ++i) { pthread_join(threads[i], NULL); }
pthread_mutex_destroy(&mtx);
printf("Final Counter: %d\n", shared_counter);
return 0; }
|
2.2 条件变量(Condition Variable)
条件变量使线程能够睡眠等待特定条件,并在其他线程修改该条件时被唤醒:
- 初始化条件变量:
pthread_cond_init()
用于创建条件变量。
- 等待条件:
pthread_cond_wait()
会将线程放入等待队列,并释放持有的互斥锁。当重新获得锁并且条件满足时,线程被唤醒。
- 发送信号:
pthread_cond_signal()
或pthread_cond_broadcast()
唤醒一个或多个等待线程。
- 销毁条件变量:
pthread_cond_destroy()
释放条件变量的相关资源。
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <stdio.h> #include <pthread.h> #include <unistd.h>
pthread_mutex_t mutex; pthread_cond_t cond_var;
int data_ready = 0;
void* producer(void* arg) { sleep(1);
pthread_mutex_lock(&mutex); data_ready = 1; printf("Producer: Data is ready.\n");
pthread_cond_signal(&cond_var); pthread_mutex_unlock(&mutex); return NULL; }
void* consumer(void* arg) { pthread_mutex_lock(&mutex);
while (data_ready == 0) { printf("Consumer: Waiting for data.\n"); pthread_cond_wait(&cond_var, &mutex); }
printf("Consumer: Data has been acquired.\n");
pthread_mutex_unlock(&mutex); return NULL; }
int main() { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond_var, NULL);
pthread_t prod_thread, cons_thread;
pthread_create(&prod_thread, NULL, producer, NULL); pthread_create(&cons_thread, NULL, consumer, NULL);
pthread_join(prod_thread, NULL); pthread_join(cons_thread, NULL);
pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond_var);
return 0; }
|
2.3 信号量(Semaphore)
信号量用于控制对多个资源的访问,提供更灵活的同步机制:
- 主要功能操作:
sem_wait()
:当资源可用时获取资源,信号量减一,否则进入休眠。
sem_post()
:释放一个资源,更新信号量。若有等待线程,则唤醒。
信号量可以被应用于各种场合:
- 二进制信号量:用于实现互斥锁功能。
- 计数信号量:用于多个资源的同步控制。
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE]; int count = 0;
sem_t empty; sem_t full; pthread_mutex_t mutex;
void* producer(void* arg) { int item; for (int i = 0; i < 10; ++i) { item = i; sem_wait(&empty); pthread_mutex_lock(&mutex);
buffer[count] = item; count++; printf("Producer produced item: %d\n", item);
pthread_mutex_unlock(&mutex); sem_post(&full); sleep(1); } return NULL; }
void* consumer(void* arg) { int item; for (int i = 0; i < 10; ++i) { sem_wait(&full); pthread_mutex_lock(&mutex);
item = buffer[count - 1]; count--; printf("Consumer consumed item: %d\n", item);
pthread_mutex_unlock(&mutex); sem_post(&empty); sleep(1); } return NULL; }
int main() { sem_init(&empty, 0, BUFFER_SIZE); sem_init(&full, 0, 0); pthread_mutex_init(&mutex, NULL);
pthread_t prod_thread, cons_thread;
pthread_create(&prod_thread, NULL, producer, NULL); pthread_create(&cons_thread, NULL, consumer, NULL);
pthread_join(prod_thread, NULL); pthread_join(cons_thread, NULL);
sem_destroy(&empty); sem_destroy(&full); pthread_mutex_destroy(&mutex);
return 0; }
|
3. 内核同步原语
在多核系统中,内核也需要合适的同步机制来确保系统调度中的数据一致性。这些主要的内核同步原语包括:
名称 |
描述 |
Spinlock自旋锁 |
利用自旋忙等待锁。适用于锁等待较短场合 |
信号量Semaphore |
基于信号量进行阻塞同步控制 |
顺序锁Seqlock |
使用读写版本号实现的锁机制 |
RCU(Read-Copy-Update) |
使用读复制更新机制实现高效的读线程处理 |
3.1 信号量(Semaphore)
在内核中,信号量用于在访问共享资源时实现阻塞同步控制。其特点在于降等待线程转至睡眠以节省CPU资源。
3.2 顺序锁(Seqlock)
顺序锁通过使用版本号机制,实现高效的数据读写锁。顺序锁特别适用于写操作频繁,但对数据一致性要求较低的场合,其提供了读写间的锁竞争转换。
3.3 RCU(Read-Copy Update)
RCU提供一种高效特殊的同步机制,适用于读多写少场合。读操作无锁框架实现迅速访问,而写操作利用副本更新和延迟指针跳转方式进行无锁响应,以减少写操作对读操作的干扰。
这些同步机制不仅提高了多线程程序的可靠性和效率,也为内核的并发管理提供了重要的技术支撑。在编写时注意三元素: 互斥锁+条件变量+状态变量,集齐三元素,确保多线程编程不出错.