学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2
一. 多线程概念
- 进程是资源分配单位,线程是 CPU 调度单位,又叫轻量级进程。
- 同一进程的线程共享地址空间、文件描述符、信号处理等,切换代价远小于进程。
- Linux 线程是用户级线程,遵循 POSIX 接口
pthread:<pthread.h>,编译加-pthread。
为什么用多线程:开销小响应快 / 共享数据通信方便 / 耗时操作放新线程不影响主线程响应 / 多 CPU 并行。
⚠ 共享内存带来便利也带来风险:访问共享资源必须做同步与互斥。
二. 线程编程
2.1 创建pthread_create
intpthread_create(pthread_t*tid,constpthread_attr_t*attr,void*(*start_rtn)(void*),void*arg);// 成功 0,失败返回错误码(别用 errno)arg传参:传结构体指针即可(线程内强转回来)。- 进程的所有信息(代码、全局/堆内存、栈、fd)对所有线程共享。
typedefstructstudent{intage;charname[20];}STU;void*create(void*arg){STU*temp=(STU*)arg;printf("STU age is %d, name is %s\n",temp->age,temp->name);}STU*stu=malloc(sizeof(STU));stu->age=20;strcpy(stu->name,"abcdefg");pthread_create(&tidp,NULL,create,(void*)stu);pthread_join(tidp,NULL);2.2 终止pthread_exit
voidpthread_exit(void*ret_val);// ret_val 交给 pthread_join- 正常终止:
pthread_exit或线程函数return。 - ⚠ 线程里调
exit()会结束整个进程!
pthread_exit | exit | |
|---|---|---|
| 影响 | 只结束当前线程 | 整个进程结束 |
清理函数(先进后出,只对pthread_exit有效)
voidpthread_cleanup_push(void(*rtn)(void*),void*arg);voidpthread_cleanup_pop(intexecute);// 0 = 不执行,非 0 = 执行pthread_cleanup_push(clean_1,"thread first handler");pthread_cleanup_push(clean_1,"thread second hadler");if(i==1)pthread_exit(NULL);// 执行清理,顺序是 second → first(后进先出)elseexit(0);// 对 exit 无效pthread_cleanup_pop(0);pthread_cleanup_pop(0);两个调用必须配套;线程正常
return或exit时清理函数不会执行。
2.3 等待pthread_join
intpthread_join(pthread_tthread,void**thread_return);// 挂起自己等指定线程结束- 只能等待同一进程的线程;⚠ 一个线程不能被多个线程 join。
void*t2_exe(void*arg){pthread_join((pthread_t)arg,NULL);// 先把线程1 join 掉printf("The second thread:\n");// 线程1结束后才打印 6~10}pthread_create(&pid1,NULL,t1_exe,NULL);pthread_create(&pid2,NULL,t2_exe,(void*)pid1);2.4 获取自身线程号
pthread_tpthread_self(void);// 同一进程内 pid 相同,线程 ID 不同三. 同步与互斥
三种机制:互斥量、信号量、条件变量。
3.1 互斥量 mutex
相当于一把锁,保证:①原子性(临界区要么全做要么不做)②唯一性(解锁前别人锁不上)③非繁忙等待(抢不到锁就挂起,不占 CPU)。
| 操作 | 函数 |
|---|---|
| 初始化 | pthread_mutex_init(&mutex, NULL)或静态PTHREAD_MUTEX_INITIALIZER |
| 加锁 | pthread_mutex_lock(&mutex)(抢不到就阻塞) |
| 尝试加锁 | pthread_mutex_trylock(&mutex)(占用时返回EBUSY,不阻塞) |
| 解锁 | pthread_mutex_unlock(&mutex)(一般由加锁者解锁) |
| 销毁 | pthread_mutex_destroy(&mutex) |
锁的 4 种属性:PTHREAD_MUTEX_TIMED_NP(默认普通锁,排队公平)/RECURSIVE_NP(嵌套锁,可重复加)/ERRORCHECK_NP(检错锁,重复加锁返回EDEADLK)/ADAPTIVE_NP(适应锁)。
示例:两个线程按顺序遍历数组
charstr[]="abcdefghijklmnopqrstuvwxyz123456789";pthread_mutex_tmutex;intindex2=0;void*t1_exe(void*arg){while(index2<strlen(str)-1){pthread_mutex_lock(&mutex);// 不加锁会出现元素重复打印printf("The %dth element is %c\n",index2,str[index2]);sleep(1);index2++;pthread_mutex_unlock(&mutex);}}// main: pthread_mutex_init(&mutex, NULL); 创建两个线程都跑 t1_exe3.2 信号量sem_t
头文件<semaphore.h>;互斥用 1 个,同步用多个。本质是 P/V 原语 + 非负计数器。
| 函数 | 作用 |
|---|---|
int sem_init(sem_t *sem, int pshared, unsigned int value); | pshared=0线程间共享(放全局/堆),非 0 进程间共享(放共享内存) |
int sem_wait(sem_t *sem); | P:值为 0 阻塞,否则减 1 |
int sem_trywait(sem_t *sem); | P 非阻塞版 |
int sem_post(sem_t *sem); | V:加 1 并唤醒一个等待者(原子) |
int sem_destroy(sem_t *sem); | 销毁 |
sem_getvalue | 取当前值 |
互斥(一个信号量,初值 1,顺序随机但不同时访问)
sem_init(&sem,0,1);// 线程1 // 线程2sem_wait(&sem);sem_wait(&sem);global++;global*=2;sem_post(&sem);sem_post(&sem);同步(两个信号量,轮流执行)
sem_init(&sem1,0,1);sem_init(&sem2,0,0);// 线程1: sem_wait(&sem1); global++; sem_post(&sem2);// 线程2: sem_wait(&sem2); global*=2; sem_post(&sem1);// 结果固定:1→2→4→5→10…3.3 条件变量
- 与互斥锁不同,它用来等待而不是上锁,通常与互斥锁配合。
- 一个线程等"条件成立"而挂起,另一个线程使"条件成立"并通知。
- 好处:避免线程反复加锁/解锁却不干活的忙等。
| 函数 | 作用 |
|---|---|
pthread_cond_init(&cond, NULL)/PTHREAD_COND_INITIALIZER | 初始化 |
pthread_cond_wait(&cond, &mutex) | 解锁 mutex → 阻塞等待 → 被唤醒后重新加锁 |
pthread_cond_timedwait(&cond, &mutex, &abstime) | 超时返回ETIME |
pthread_cond_signal(&cond) | 唤醒一个等待线程 |
pthread_cond_destroy(&cond) | 释放 |
⚠
pthread_cond_wait返回后必须重新判断条件:解锁→等待→加锁这三步不是原子操作,等待期间条件可能已被别的线程改变。
pthread_mutex_tmutex;pthread_cond_tcond;inti=1;void*thread1(void*arg)// i 为 9 的倍数时通知 thread2{for(i=1;i<=MAX;i++){pthread_mutex_lock(&mutex);if(i%9==0)pthread_cond_signal(&cond);elseprintf("In thread1:%d\n",i);pthread_mutex_unlock(&mutex);sleep(1);}}void*thread2(void*flag)// 不是 9 的倍数就等{while(i<MAX){pthread_mutex_lock(&mutex);if(i%9!=0)pthread_cond_wait(&cond,&mutex);if(i%9==0)printf("In thread2:%d\n",i);pthread_mutex_unlock(&mutex);sleep(1);}}四. 三种机制怎么选
| 机制 | 着眼点 |
|---|---|
| 互斥量 | 共享资源的互斥访问(临界区同时只进一个线程) |
| 信号量 | 互斥(1 个)+ 同步(多个) |
| 条件变量 | 等待某个条件成立;配合互斥量避免忙等 |
互斥/同步会降低一点效率,但能保证共享资源一致;按需选择即可。