多线程
1️⃣线程概述
✨✨✨
- 进程是CPU分配资源的最小单位,线程是CPU调度的最小单位。
- 每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,*需要较大的系统开销。为了减少系统开销,从进程中演化出了线程。
- 为了让进程完成一定的工作,进程必须至少包含一个线程(主线程0)。
- 线程存在于进程中,共享进程的资源(线程存放在栈区)
- 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。所以线程间拥有更方便的通信机制。
2️⃣pthread库
✨✨✨
- POSIX 标准定义了一套线程操作相关的函数, 即pthread 库函数,用于让程序员更加方便地操作管理线程,函数名都是以前缀 pthread_ 开始,使用时要包含 <pthread.h>,而且在链接的时候要手动链接 pthread 这个库,如:gcc a.c -lpthread -o a.out。
- pthread 库中多线程开发的最基本概念主要包含三点:线程,互斥锁,条件,涉及的API:
对象 | 操作 | API |
---|---|---|
线程 | 创建 | pthread_create |
线程 | 退出 | pthread_exit |
线程 | 等待 | pthread_join |
互斥锁 | 创建 | pthread_mutex_init |
互斥锁 | 销毁 | pthread_mutex_destroy |
互斥锁 | 加锁 | pthread_mutex_lock |
互斥锁 | 解锁 | pthread_mutex_unlock |
条件 | 创建 | pthread_cond_init |
条件 | 销毁 | pthread_cond_destroy |
条件 | 触发 | pthread_cond_signal |
条件 | 广播 | pthread_cond_broadcast |
条件 | 等待 | pthread_cond_wait/pthread_cond_timedwait |
3️⃣线程的创建、等待和退出
✨✨✨
API(头文件pthread.h):
-
❤️获取线程号
pthread_t pthread_self(void);
-
功能: 获取线程号
-
参数: 无
-
返回值: 调用线程的线程 ID (posix描述的线程ID),不会失败
-
-
❤️线程号比较
int pthread_equal(pthread_t t1, pthread_t t2);
-
功能: 判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
-
参数: t1,t2:待判断的线程号。
-
返回值: 相等返回非0 ;不相等返回0
-
-
❤️线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
-
功能: 创建一个线程。
-
参数:
- thread:存放线程标识符。
- attr:用于定制各种不同的线程属性,设置为NULL就是创建默认属性的线程。
- start_routine:线程函数的入口地址。
- arg:传给线程函数的参数。
-
返回值: 成功返回0,否则返回错误编号
-
-
❤️等待并回收线程资源
int pthread_join(pthread_t thread, void **rval_ptr);
-
功能: 等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
-
参数:
- thread:被等待的线程号。
- retval:用来存储线程退出状态的指针的地址。
-
返回值:成功返回0,否则返回错误编号
-
-
❤️线程分离
int pthread_detach(pthread_t thread);
-
功能: 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。(进程没有此机制)
-
参数: thread 线程号
-
返回值: 成功返回0,否则返回错误编号
-
-
❤️线程退出
void pthread_exit(void *retval);
-
功能: 退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
-
参数: retval 存储线程退出状态的指针。
-
返回值: 无
-
代码示例:
-
th.c:
#include<stdio.h> #include<pthread.h> void *start_rtn (void *arg) { static int t1Exit = 10; //线程返回值,注意为static类型 printf("this is thread:%ld\n",(unsigned long)pthread_self()); printf("th data:%s\n",(char*)arg); pthread_exit((void *)&t1Exit); //线程退出 } int main() { pthread_t th = 0; char *data ="NB 666 My Baby!"; if(pthread_create(&th,NULL,start_rtn,(void*)data) != 0){ perror("thread error"); } int* thret; pthread_join(th,(void**)thret); //主线程阻塞等待线程th退出 printf("th return:%d\n",thret); //打印线程退出值 return 0; }
4️⃣互斥锁mutex
🔒✨✨✨🔑
- 互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即🔒加锁( lock )和🔑解锁( unlock )。
- 举个例子来说就是好几个人都想上厕所🚽,但是厕所一次仅供一人享用,要想保证上厕所过程不被打断进去就要把门锁上🔒,对于其他人来说要一直等到门开锁🔑才能进去。
- 互斥锁的操作流程
- 🔒在访问共享资源后临界区域前,对互斥锁进行加锁。
- 🔑在访问完成后释放互斥锁上的锁。
- 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
- 互斥锁的数据类型是:pthread_mutex_t。
API(头文件pthread.h):
-
❤️互斥锁初始化
-
动态初始化:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
- 功能: 初始化一个互斥锁。
- 参数:
- mutex:互斥锁地址。类型是 pthread_mutex_t 。
- attr:设置互斥量的属性,NULL未默认属性
- 返回值:成功:0,成功申请的锁默认是打开的;失败返回错误码
-
静态初始化:
-
可以使用宏 PTHREAD_MUTEX_INITIALIZER (只对静态分配的互斥量)静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
-
🔒互斥锁加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
功能: 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,直到互斥锁解锁后再上锁。
-
参数: mutex:互斥锁地址。
-
返回值:成功0,否则返回错误码
-
-
🔑互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t * mutex);
-
功能: 对指定的互斥锁解锁。
-
参数: mutex:互斥锁地址。
-
返回值:成功0,否则返回错误码
-
-
💔互斥锁销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
-
功能: 销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
-
参数: mutex 互斥锁地址。
-
返回值:成功0,否则返回错误码
-
示例代码
-
mutex.c:
#include<stdio.h> #include<pthread.h> pthread_mutex_t mutex; void *start_rtn1 (void *arg) { int i=5; pthread_mutex_lock(&mutex); while(i--){ printf("this is thread1:%ld\n",(unsigned long)pthread_self()); printf("data:%d\n",(*(int*)arg)++); } pthread_mutex_unlock(&mutex); } void *start_rtn2 (void *arg) { int i=5; pthread_mutex_lock(&mutex); while(i--){ printf("this is thread2:%ld\n",(unsigned long)pthread_self()); printf("data:%d\n",(*(int*)arg)++); } pthread_mutex_unlock(&mutex); } int main() { pthread_t th1 = 0; pthread_t th2 = 0; pthread_mutex_init(&mutex,NULL); int data = 0; if(pthread_create(&th1,NULL,start_rtn1,(void*)&data) != 0){ perror("thread1 error"); } if(pthread_create(&th2,NULL,start_rtn2,(void*)&data) != 0){ perror("thread2 error"); } pthread_join(th1,NULL); pthread_join(th2,NULL); pthread_mutex_destroy(&mutex); return 0; }
-
终端显示:
5️⃣条件变量
- 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
- 条件变量主要包括两个动作:一个线程等待“条件变量的成立”,条件不成立时线程挂起;另一个线程修改条件,并判断条件是否成立,当“条件成立”时,唤醒等待条件成立的线程。
API(头文件pthread.h):
-
❤️条件变量初始化
-
动态初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
- 功能: 初始化一个条件变量。
- 参数:
- cond:指向条件变量,条件变量用pthread_cond_t数据类型表示
- attr:除非需要创建一个非默认属性的条件变量,否则attr参数可以设置为NULL。
- 返回值:成功0,否则返回错误码
-
静态初始化:
-
把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
-
-
⌚️等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
- 功能:
- pthread_cond_wait:当条件不到来时,一直阻塞
- pthread_cond_timedwait:当条件不到来时,仅仅只阻塞一定的时长
- 参数:
- cond:创建的条件参数
- mutex:创建的线程参数
- timeout:指定等待的时间(timedwait)
- 返回值:成功0,否则返回错误码
- 功能:
-
📣 触发和广播
//触发 int pthread_cond_signal(pthread_cond_t *cond); //广播 int pthread_cond_broadcast(pthread_cond_t *cond);
- 触发表示唤醒任意一个等待该条件的线程,而广播表示唤醒所有等待该条件的线程。
- 参数:cond 条件变量
- 返回值:成功0,否则返回错误码
-
💔条件变量的销毁
int pthread_cond_destroy(pthread_cond_t *cond);
-
功能: 销毁指定的一个条件变量
-
参数: cond 条件变量
-
返回值:成功0,否则返回错误码
-
示例代码:
-
生产者——消费者模型cond.c:
#include <stdio.h> #include <pthread.h> #define MAX 5 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t notfull = PTHREAD_COND_INITIALIZER; //是否队满 pthread_cond_t notempty = PTHREAD_COND_INITIALIZER; //是否队空 int top = 0; int bottom = 0; void* produce(void* arg) { int i; for ( i = 0; i < MAX*2; i++) { pthread_mutex_lock(&mutex); while ((top+1)%MAX == bottom) { printf("full! producer is waiting\n"); pthread_cond_wait(¬full, &mutex);//等待队不满 } top = (top+1) % MAX; printf("now top is %d\n", top); pthread_cond_signal(¬empty);//发出队非空的消息 pthread_mutex_unlock(&mutex); } return (void*)1; } void* consume(void* arg) { int i; for ( i = 0; i < MAX*2; i++) { pthread_mutex_lock(&mutex); while ( top%MAX == bottom) { printf("empty! consumer is waiting\n"); pthread_cond_wait(¬empty, &mutex);//等待队不空 } bottom = (bottom+1) % MAX; printf("now bottom is %d\n", bottom); pthread_cond_signal(¬full);//发出队不满的消息 pthread_mutex_unlock(&mutex); } return (void*)2; } int main(int argc, char *argv[]) { pthread_t thid1; pthread_t thid2; pthread_t thid3; pthread_t thid4; int ret1; int ret2; int ret3; int ret4; pthread_create(&thid1, NULL, produce, NULL); pthread_create(&thid2, NULL, consume, NULL); pthread_create(&thid3, NULL, produce, NULL); pthread_create(&thid4, NULL, consume, NULL); pthread_join(thid1, (void**)&ret1); pthread_join(thid2, (void**)&ret2); pthread_join(thid3, (void**)&ret3); pthread_join(thid4, (void**)&ret4); return 0; }