一、线程分离属性
1.1 什么是分离属性
之前学的线程需要用 pthread_join() 回收,否则会变成僵尸线程。如果不需要等待线程的返回值,可以把它设为分离状态: 线程结束后系统自动回收资源,不用手动 join。
| 属性 | 特点 |
|---|---|
| 加入(默认) | 需要 pthread_join() 回收 |
| 分离 | 线程结束自动回收,无需 join |
1.2 设置分离属性
c
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设为分离
pthread_create(&tid, &attr, thread_func, NULL); // 创建分离线程
pthread_attr_destroy(&attr); // 销毁属性
c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_func(void *arg)
{
printf("分离线程执行中\n");
sleep(1);
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
// 不需要 pthread_join,线程结束后自动回收
sleep(2);
return 0;
}
二、多进程 vs 多线程
| 对比项 | 多进程 | 多线程 |
|---|---|---|
| 运行效率 | 低(创建开销大) | 高(共享进程空间) |
| 安全性 | 高(进程独立,一个崩溃不影响其他) | 低(线程崩溃导致整个进程结束) |
| 通信方式 | 复杂(需要IPC) | 方便(共享全局变量) |
| 通信复杂度 | 高 | 低(但需要处理同步) |
三、线程间通信与资源竞争
3.1 线程间通信方式
线程之间通过共享全局变量 来通信,比进程间通信简单得多。但多个线程同时修改同一个全局变量时,会出现资源竞争。
c
int money = 100;
void *withdraw(void *arg)
{
if (money >= 50) {
usleep(100); // 模拟时间片切换
money -= 50;
}
}
两个线程同时取钱,可能都通过判断,导致最终结果错误。
四、互斥锁
4.1 基本概念
互斥锁(Mutex)让多个线程排着队访问共享资源,谁先拿到锁谁先操作,操作完解锁,其他人才能继续。
4.2 函数接口
c
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化
// 或
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁(阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁
4.3 使用示例
c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int money = 100;
pthread_mutex_t mutex;
void *withdraw(void *arg)
{
pthread_mutex_lock(&mutex); // 加锁
if (money >= 50) {
usleep(100);
money -= 50;
printf("取钱成功,余额:%d\n", money);
} else {
printf("余额不足\n");
}
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
int main(void)
{
pthread_t t1, t2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, withdraw, NULL);
pthread_create(&t2, NULL, withdraw, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
4.4 死锁
两个线程各自拿着一把锁,同时等对方释放,谁都动不了,程序卡死。
避免死锁的方法:
-
使用
pthread_mutex_trylock()替代pthread_mutex_lock()(不阻塞,拿不到锁就返回) -
多把锁时保持一致的加锁顺序
五、信号量
5.1 什么是信号量
信号量是一种资源计数器,用来控制同时访问共享资源的线程数量。比如限制最多3个线程同时访问某资源。
5.2 函数接口
c
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem); // 申请资源(P操作),资源数>0则减1,=0则阻塞
int sem_post(sem_t *sem); // 释放资源(V操作),资源数+1
-
pshared:0 表示线程间共享,非0 表示进程间共享 -
value:初始资源数量
5.3 互斥锁 vs 信号量
| 互斥锁 | 信号量 | |
|---|---|---|
| 本质 | 锁(只能一个线程持有) | 计数器(可多个线程同时访问) |
| 操作 | lock / unlock | wait / post |
| 适用场景 | 保护临界区 | 控制并发数量 |
六、今日总结
今天学了线程同步的核心内容:
线程分离:
-
pthread_attr_setdetachstate()设置分离属性 -
分离线程结束自动回收,无需
join
互斥锁:
-
pthread_mutex_lock()加锁,pthread_mutex_unlock()解锁 -
保护临界区,防止资源竞争
-
死锁:多锁加锁顺序不一致导致
信号量:
-
sem_wait()申请资源,sem_post()释放资源 -
控制同时访问的线程数量
今日感悟:
线程共享数据方便,但是绕不开资源竞争的问题。互斥锁让多个线程排着队访问共享资源,虽然牺牲了一点性能,但换来了数据安全。信号量更灵活,能控制同时访问的线程个数,适合限流场景。多进程安全但慢,多线程快但不安全;锁能保护数据但可能死锁。没有完美的方案,只有适合场景的选择。