多线程
多线程是什么?怎么使用?多线程和多进程有什么区别?本篇就作一些简单介绍带你入门
写在前面
本篇仅作简单介绍,内容适合入门级别,可通过目录查看。重点在于熟悉每一个经典使用模式
文中相关api就不做过多介绍,不然字数就太多了,可自行去api手册了解
语言不保证严谨,内容也不保证完全,欢迎指正错误、补充
如果你已经看过我之前的多进程博客(并且能适应我的破烂语言组织),这篇文章会让你更容易理解。多线程和多进程在很多概念上是相通的,但也有本质区别。
一、多线程基础
一、进程 vs 线程(简单区分)
其实大多数人心中对这两个有一个模糊的对比,那么简单来讲就是:一个工厂(进程)里面有多个工人(线程),工人共享工厂资源,协作完成一个任务。
二、多线程基础
- 多线程也是实现多任务并发的一种
- 进程是资源分配的基本单位,线程是任务器进程进行任务调度的最小单位
- 一个进程可以拥有多个线程,同一个进程中多个线程共享进程资源
- 因为共享进程资源,多线程的主要问题是竞态,会发生资源抢占问题,也每多进程安全
- 多线程执行顺序是按时间片轮转的
- 一个进程中的线程占用越多,任务器分配给该进程的时间片就越多
三、多线程相关基础api
- C库没有提供多线程相关操作,对于多线程编程,要依赖第三方库
- 头文件:#include <pthread.h>
- 编译时也要加上
-lpthread 或 -pthread
链接上这个库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t, thread);
void pthread_exit(void *retval);
pthread_t pthread_self(void);
|
四、第一个多线程程序
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
| #include <stdio.h> #include <pthread.h>
void *task(void *arg) { printf("分支线程, tid = %#x\n", pthread_self()); sleep(3); pthread_exit(NULL); }
int main(int argc, const char *argv[]) { pthread_t tid = -1; if(pthread_create(&tid, NULL, &task, NULL) != 0) { printf("pthread_create error\n"); return -1; } printf("这里是主线程, tid = %#x\n", tid); pthread_detach(tid); sleep(5); return 0; }
|
二、线程的同步互斥
- 由于同一个进程的多个线程会共享进程的资源,这些被共享的资源称为临界资源
- 多个线程抢占进程资源的现象称为竞态
- 类似于解决多进程共享内存问题使用了信号量集的同步功能,多线程的竞态问题也可以用同步控制来解决
一、互斥锁
- 像名字一样,是一个锁,某个线程拥有这个锁后就能“锁住”自己正在访问的临界资源,其他线程打不开这个锁,也就访问不了那一片的临界资源了。
- 互斥锁的本质也是一个特殊的临界资源,当该临界资源被某个线程所拥有后,其他线程就不能拥有该资源(同一时刻,一个互斥锁只
能被一个线程所拥有)
互斥锁相关api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 1、创建一个互斥锁:只需定义一个pthread_mutex_t 类型的变量即创建了一个互斥锁 pthread_mutex_t mutex;
2、初始化互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
3、获取锁资源 int pthread_mutex_lock(pthread_mutex_t *mutex); 功能:获取锁资源,如果要获取的互斥锁已经被其他线程锁定,那么该函数会阻塞,直到能够获取锁资源
4、释放锁资源,释放对锁的拥有权 int pthread_mutex_unlock(pthread_mutex_t *mutex);
5、销毁互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);
|
- 经典使用模式
- 可以把互斥锁的操作去掉/加上,分别运行程序,做一个对比
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
| #include <stdio.h> #include <pthread.h>
pthread_mutex_t mutex;
int num = 1000;
void *task1(void *arg) { while(1) { sleep(1); pthread_mutex_lock(&mutex); num -= 10; printf("线程1拿走了10, 剩余%d\n", num); pthread_mutex_unlock(&mutex); } }
void *task2(void *arg) { while(1) { sleep(1); pthread_mutex_lock(&mutex); num -= 20; printf("线程2拿走了20, 剩余%d\n", num); pthread_mutex_unlock(&mutex); } }
int main(int argc, const char *argv[]) { pthread_mutex_init(&mutex, NULL); pthread_t tid1, tid2; if(pthread_create(&tid1, NULL, &task1, NULL) != 0) { printf("pthread_create tid1 error\n"); return -1; } if(pthread_create(&tid2, NULL, &task2, NULL) != 0) { printf("pthread_create tid2 error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&mutex); printf("主线程:所有线程都执行完毕\n"); return 0; }
|
二、无名信号量
- 本质上也是一个特殊的临界资源,内部维护了一个value值,当某个进行想要执行之前,先申请该无名信号量的value资源,如果value值大于0,则申请资源函数接触阻塞,继续执行后续操作。如果value值为0,则当前申请资源函数会处于阻塞状态,直到其他线程将该value值增加到大于0。
- 常用于生产者消费者模型
无名信号量相关api
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <semaphore.h> 1、创建无名信号量:只需定义一个sem_t 类型的变量即可 sem_t sem; 2、初始化无名信号量 int sem_init(sem_t *sem, int pshared, unsigned int value);
3、申请无名信号量的资源(P操作) int sem_wait(sem_t *sem);
4、释放无名信号量的资源(V操作) int sem_post(sem_t *sem);
5、销毁无名信号量 int sem_destroy(sem_t *sem);
|
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 <semaphore.h>
sem_t sem;
void *task1(void *arg) { int num = 5; while(num--) { sleep(1); printf("生产了一个雪糕\n"); sem_post(&sem); } pthread_exit(NULL); }
void *task2(void *arg) { int num = 5; while(num--) { sem_wait(&sem); printf("消费了一个雪糕,好吃\n"); } pthread_exit(NULL); }
int main(int argc, const char *argv[]) { sem_init(&sem, 0, 0); pthread_t tid1, tid2; if(pthread_create(&tid1, NULL, task1, NULL) != 0) { printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0) { printf("tid2 create error\n"); return -1; }
printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); pthread_join(tid1, NULL); pthread_join(tid2, NULL);
sem_destroy(&sem); }
|
三、条件变量
- 条件变量本质上也是一个临界资源,他维护了一个队列,当消费者线程想要执行时,先进入队列中
等待生产者的唤醒。执行完生产者,再由生产者唤醒在队列中的消费者,这样就完成了生产者和消费者
之间的同步关系。
- 多个消费者进入队列中也是互斥的,有竞态,为了保证顺序,消费者需要互斥锁来进行互斥操作
条件变量相关api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 1、创建一个条件变量,只需定义一个pthread_cond_t类型的全局变量即可 pthread_cond_t cond;
2、初始化条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
3、消费者线程进入等待队列 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 功能:将线程放入休眠等待队列,等待其他线程的唤醒
4、生产者线程唤醒休眠队列中的任务 int pthread_cond_broadcast(pthread_cond_t *cond); 功能:唤醒条件变量维护的队列中的所有消费者线程
int pthread_cond_signal(pthread_cond_t *cond); 功能:唤醒条件变量维护的队列中的第一个进入队列的消费者线程
5、销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond);
|
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 80 81 82 83 84 85 86
| #include <stdio.h> #include <pthread.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
void *task_c(void *arg) { int num = 9; while(num--) { sleep(1); printf("%#x:生产了一个雪糕,快来吃\n"); pthread_cond_signal(&cond); } pthread_exit(NULL); }
void *task(void *arg) { int i = *(int *)arg; pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); printf("%#x:消费了一个雪糕,好吃\n"); pthread_mutex_unlock(&mutex); pthread_exit(NULL); }
int main(int argc, const char *argv[]) { pthread_cond_init(&cond, NULL); pthread_mutex_init(&mutex, NULL); pthread_t tid; if(pthread_create(&tid, NULL, task_c, NULL) != 0) { printf("tid create error\n"); return -1; } pthread_t tids[5]; for(int i = 0; i < 5; i++) { if(pthread_create(&tids[i], NULL, task, &i) != 0) { printf("tid%d create error\n", i); return -1; } } pthread_join(tid, NULL); for(int i = 0; i < 5; i++) { pthread_join(tid[i], NULL); }
pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); return 0; }
|