一、线程属性设置
1. 线程属性概述
在Linux中,每个线程都有其特定的属性,我们可以通过线程属性对象来设置这些属性。
2. 线程属性相关函数
pthread_attr_init
c
int pthread_attr_init(pthread_attr_t *attr);
-
功能:初始化线程属性为默认值
-
参数:attr - 存放线程属性空间首地址
-
返回值:成功返回0,失败返回非0
pthread_attr_destroy
c
int pthread_attr_destroy(pthread_attr_t *attr);
-
功能:销毁线程属性
-
参数:attr - 存放线程属性空间首地址
-
返回值:成功返回0,失败返回非0
pthread_attr_setdetachstate
c
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
-
功能:设置线程加入/分离属性
-
参数:
-
attr:存放线程属性空间首地址
-
detachstate:线程属性
-
PTHREAD_CREATE_DETACHED:分离属性 -
PTHREAD_CREATE_JOINABLE:加入属性
-
-
-
返回值:成功返回0,失败返回非0
3. 线程的两种属性
加入属性 (JOINABLE)
-
线程结束后需要手动回收线程空间(使用pthread_join)
-
可以回收线程结束的状态
-
可以实现线程同步
分离属性 (DETACHED)
-
线程结束后自动回收线程空间
-
不需要手动回收空间,由系统自动回收
-
可以实现多任务先结束先被回收,后结束后被回收
二、线程间通信
1. 线程间通信方式
同一进程空间内的所有线程共享数据段和堆区,因此可以通过以下方式进行通信:
-
全局变量
-
静态变量
-
堆区空间
2. 资源竞争问题
多线程操作共享变量时会引入资源竞争,需要通过互斥锁来解决。
三、互斥锁
1. 原子操作
不会被CPU任务调度打断的一次最小操作称为原子操作。
2. 互斥锁的概念
-
避免多线程资源竞争
-
使用资源前加锁,使用资源结束后解锁
-
加锁后无法再次加锁,必须等到解锁后才能继续加锁
3. 临界代码与临界区
加锁和解锁之间的代码称为临界代码或临界区,这部分代码不可能同时被多个CPU任务执行。
4. 互斥锁函数接口
pthread_mutex_init
c
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
-
功能:互斥锁初始化
-
参数:
-
mutex:互斥锁空间首地址
-
attr:互斥锁属性,默认传入NULL
-
-
返回值:成功返回0,失败返回非0
pthread_mutex_destroy
c
int pthread_mutex_destroy(pthread_mutex_t *mutex);
-
功能:互斥锁销毁
-
参数:mutex - 互斥锁空间首地址
pthread_mutex_lock
c
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
功能:互斥锁加锁
-
参数:mutex - 互斥锁空间首地址
pthread_mutex_unlock
c
int pthread_mutex_unlock(pthread_mutex_t *mutex);
-
功能:互斥锁解锁
-
参数:mutex - 互斥锁空间首地址
5. 死锁
死锁的概念
多任务通信过程中由于加锁导致多个任务均无法向下执行的状态称为死锁。
死锁产生的4个必要条件
-
互斥条件:资源一次只能被一个线程使用
-
不可剥夺条件:资源只能由持有者主动释放
-
请求保持:线程持有资源的同时请求其他资源
-
循环等待:存在循环等待链
死锁的解决方法
-
使用
pthread_mutex_trylock替代pthread_mutex_lock,无法加锁时进行异常处理 -
保持加锁顺序一致
四、信号量
1. 信号量的作用
信号量可以实现多线程间的同步,让多个任务具有先后顺序关系。
同步与异步
-
同步:拥有严格的先后执行的逻辑顺序关系
-
异步:代码执行流程没有任何关联性
2. 信号量的特性
信号量是一个资源,可以初始化、销毁、申请和释放:
-
如果资源数 > 0,申请资源时资源数减1
-
如果资源数 = 0,申请资源时会阻塞等待,直到有人释放资源
-
释放不会阻塞,让资源数加1
3. 信号量函数接口
sem_init
c
int sem_init(sem_t *sem, int pshared, unsigned int value);
-
功能:信号量初始化
-
参数:
-
sem:信号量空间首地址
-
pshared:信号量作用域
-
0:线程间共享
-
非0:进程间共享
-
-
value:信号量的初始值
-
-
返回值:成功返回0,失败返回非0
sem_destroy
c
int sem_destroy(sem_t *sem);
-
功能:销毁无名信号量
-
参数:sem - 信号量空间首地址
-
返回值:成功返回0,失败返回非0
sem_wait
c
int sem_wait(sem_t *sem);
-
功能:申请资源,让资源数减1,如果资源数为0则阻塞等待
-
参数:sem - 信号量空间首地址
-
返回值:成功返回0,失败返回非0
sem_post
c
int sem_post(sem_t *sem);
-
功能:释放资源,让资源数加1
-
参数:sem - 信号量空间首地址
-
返回值:成功返回0,失败返回非0
五、经典练习
练习要求
创建3个线程任务,分别打印A、B、C,要求打印顺序总是ABC。
思路分析:
-
使用三个信号量来控制打印顺序
-
线程1打印A后释放线程2的信号量
-
线程2打印B后释放线程3的信号量
-
线程3打印C后释放线程1的信号量
c
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_a, sem_b, sem_c;
void *print_a(void *arg) {
while(1) {
sem_wait(&sem_a);
printf("A");
fflush(stdout);
sem_post(&sem_b);
}
return NULL;
}
void *print_b(void *arg) {
while(1) {
sem_wait(&sem_b);
printf("B");
fflush(stdout);
sem_post(&sem_c);
}
return NULL;
}
void *print_c(void *arg) {
while(1) {
sem_wait(&sem_c);
printf("C");
fflush(stdout);
sem_post(&sem_a);
}
return NULL;
}
int main() {
pthread_t tid1, tid2, tid3;
sem_init(&sem_a, 0, 1); // 初始只有A可以执行
sem_init(&sem_b, 0, 0);
sem_init(&sem_c, 0, 0);
pthread_create(&tid1, NULL, print_a, NULL);
pthread_create(&tid2, NULL, print_b, NULL);
pthread_create(&tid3, NULL, print_c, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
sem_destroy(&sem_a);
sem_destroy(&sem_b);
sem_destroy(&sem_c);
return 0;
}
六、综合作业
作业要求
创建4个线程,分别执行不同间隔的打印任务,并通过主线程接收字符来控制线程的暂停/恢复:
-
线程1:间隔1秒打印"采集线程正在执行"
-
线程2:间隔2秒打印"记录线程正在执行"
-
线程3:间隔5秒打印"告警线程正在执行"
-
线程4:间隔10秒打印"日志线程正在执行"
-
主线程从终端接收字符:
-
接收到'A':控制线程1暂停/恢复
-
接收到'B':控制线程2暂停/恢复
-
接收到'C':控制线程3暂停/恢复
-
接收到'D':控制线程4暂停/恢复
-



关键知识点:
-
使用信号量或标志位控制线程的暂停与恢复
-
互斥锁保护共享标志位
-
线程同步机制的应用
总结
线程属性设置和线程间通信是多线程编程的核心内容:
-
线程属性决定线程的回收方式
-
互斥锁解决资源竞争问题
-
信号量实现线程同步
-
合理组合这些机制可以实现复杂的多线程应用
掌握这些知识对于编写高效、稳定的多线程程序至关重要