Linux之多线程
线程与进程区别:
- 内存空间
一个进程中多个线程共享同一个内存空间
多个进程拥有独立的内存空间 - 进程/线程间通讯
线程间通讯方式简单
进程间通讯方式复杂
联系比较紧密的任务,在并发时,优先现在多线程,任务联系不紧密,比较独立的任务建议选择多进程
在 Linux 系统有很多命令可以查看进程,包括 pidstat 、top 、ps ,可以查看进程,也可以查看一个进程下的线程
查看线程
pidstat命令
ubuntu 下需要安装 sysstat 工具之后,可以支持 pidstat
sudo apt install sysstat
选项
-t : 显示指定进程所关联的线程
-p : 指定 进程 pid
eg:查看进程号为153729,所关联的线程
pidstat -t -p 153729
top 命令
top 命令查看某一个进程下的线程,需要用到 -H 选项再结合 -p 指定 pid
eg:
top -H -p 153729
ps 命令
ps 命令结合 -T 选项就可以查看某个进程下所有线程
eg:
ps -T -p 153729
线程的创建
创建线程调用 pthread_create 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
函数功能 :创建一个子线程
函数参数
- thread : 线程 ID 变量指针
- attr : 线程属性,默认属性可设置为 NULL
- start_routine : 线程执行函数
- arg : 线程执行函数的参数
函数返回值
- 成功 : 返回 0
- 失败 : 返回 错误码
注意 :
一旦子线程创建成功,则会被独立调度执行,并且与其他线程 并发执行
在编译时需要链接 -lpthread
示例:创建一个线程,并打印线程 ID
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *do_thread(void *arg){
printf("Thread start\n");
}
int main(){
pthread_t thread_id;
int ret=pthread_create(&thread_id,NULL,do_thread,NULL);
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
printf("pthread_id=%ld\n",thread_id);
return 0;
}
gcc thread.c -pthread
打印结果:
pthread_id=139660421760768
线程退出
线程退出使用 pthread_exit 函数
函数头文件
#include <pthread.h>
函数原型:
void pthread_exit(void *retval);
函数功能 :让线程退出,并返回值
函数参数:
retval : 线程返回值,通过指针传递
函数返回值 :
成功 :返回 0
失败 : 返回 -1
当主线程调用 pthread_exit 函数时,进程不会结束,也不会导致其他子线程退出
任何线程调用 exit 函数会让进程结束
线程等待
主线程需要等待子线程退出,并释放子线程资源
线程等待调用 pthread_join 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_join(pthread_t thread, void **retval);
函数功能 等待子进程退出,并释放子线程资源
函数参数
- thread : 线程 ID
- retval : 获取线程退出值的指针
函数返回值
成功 : 返回 0
失败 : 返回 错误码
示例:创建一个线程,主线程等待子线程退出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *do_thread(void *arg){
printf("Thread start\n");
pthread_exit(NULL);//线程退出
}
int main(){
pthread_t thread_id;
int ret=pthread_create(&thread_id,NULL,do_thread,NULL);
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
printf("pthread_id=%ld\n",thread_id);
pthread_join(thread_id,NULL);//等待线程退出
return 0;
}
线程分离
线程分为可结合的与可分离的
-
可结合
1.可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。2.线程创建的默认状态为 可结合的,可以由其他线程调用pthread_join函数等待子线程退出并释放相关资源
-
可分离
1.不能被其他线程回收或者杀死的,
2.该线程的资源在它终止时由系统来释放
函数头文件
#include <pthread.h>
函数原型
int pthread_detach(pthread_t thread);
函数功能:设置在线程退出后,由操作系统自动释放该线程的资源
函数返回值
- 成功 : 返回 0
- 失败 : 返回 -1
注意:
线程分离函数不会阻塞线程的执行
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *do_thread(void *arg){
printf("Thread start\n");
pthread_exit(NULL);//线程退出
}
int main(){
pthread_t thread_id;
int ret=pthread_create(&thread_id,NULL,do_thread,NULL);
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
printf("pthread_id=%ld\n",thread_id);
//pthread_join(thread_id,NULL);//等待线程退出
pthread_detach(thread_id);//不会阻塞主线程
while(1);//等待子线程退出
return 0;
}
创建多个线程
示例:创建两个个线程, 并等待两个子线程退出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
void *do_thread(void *arg){
for(int i=0;i<5;i++){
printf("%d\n",i);
sleep(1);
}
pthread_exit(NULL);//线程退出
}
int main(){
pthread_t thread_id[2]={0};
for(int i=0;i<2;i++){
int ret=pthread_create(&thread_id[i],NULL,do_thread,NULL);
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
printf("pthread_id=%ld\n",thread_id[i]);
}
//可以使用 for循环释放子线程资源
pthread_join(thread_id[0],NULL);//等待线程退出
pthread_join(thread_id[1],NULL);
return 0;
}
线程间的通讯
在进程里面使用的通讯在线程里面同样可以使用
示例1:主线程传递一个整型变量给子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
void *do_thread(void *arg){
printf("number=%d\n",*(int *)arg);
pthread_exit(NULL);//线程退出
}
int main(){
pthread_t thread_id;
int number=100;
int ret=pthread_create(&thread_id,NULL,do_thread,(void *)&number);//传入参数
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
pthread_join(thread_id,NULL);//等待线程退出
return 0;
}
示例2:子线程传递
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
void *do_thread(void *arg){
static int number=100;
pthread_exit((void*)&number);//线程退出
}
int main(){
pthread_t thread_id;
void *rec_num=NULL;
int ret=pthread_create(&thread_id,NULL,do_thread,NULL);
if(ret!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
pthread_join(thread_id,&rec_num);//等待线程退出
printf("number=%d\n",*(int *)rec_num);
return 0;
}
线程互斥锁
进程中的信号量也适用于线程
初始化互斥锁
线程互斥锁的初始化方式主要分为两种
1、静态初始化
定义 pthread_mutex_t 类型的变量,然后对其初始化为PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2、动态初始化
动态初始化主要涉及两个函数 pthread_mutex_init 函数 与pthread_mutex_destroy 函数
pthread_mutex_init 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_init(pthread_mutex_t*restrict mutex,
const pthread_mutexattr_t *restrict attr);
函数功能:初始化线程互斥锁
函数参数
- mutex : 线程互斥锁对象指针
- attr : 线程互斥锁属性
函数返回值
-
成功 : 返回 0
-
失败 : 返回 错误编码
pthread_mutex_destroy 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能:销毁线程互斥锁
函数参数
- mutex : 线程互斥锁指针
函数返回值
- 成功 : 返回 0
- 失败 : 返回 错误编码
线程互斥锁的操作主要分为获取锁(lock) 与释放锁(unlock) , 具体函数描述如下
获取锁(lock)
pthread_mutex_lock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能:
将互斥锁进行锁定,如果已经锁定,则阻塞线程
函数参数
- mutex : 线程互斥锁指针
函数返回值
成功 : 返回 0
失败 : 返回 错误码
释放锁(unlock)
pthread_mutex_unlock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数功能:解除互斥锁锁定状态,解除后,所有线程可以重新竞争锁
函数参数
- mutex : 线程互斥锁对象的指针
函数返回值
成功 : 返回 0
失败 : 返回 错误码
示例:创建两个线程,分别对全局变量进行 +1 操作, 分别对比使用互斥锁和没有使用互斥锁
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
static int global=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *do_thread(void *arg){
int loops=*(int *)arg;
int i,temp=0;
for(i=0;i<loops;i++){
pthread_mutex_lock(&mutex);
temp=global;
temp++;
global=temp;
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc,char *argv[]){
pthread_t thread_id[2]={0};
int err;
int loops=atoi(argv[1]);
for(int i=0;i<2;i++){
err=pthread_create(&thread_id[i],NULL,do_thread,(void *)&loops);
if(err!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
}
pthread_join(thread_id[0],NULL);
pthread_join(thread_id[1],NULL);
printf("%d\n",global);
return 0;
}
没有使用互斥锁时,输出结果如下:
./a.out 1000000
global = 1321859
使用互斥锁时,输出结果如下:
global = 2000000
使用动态初始化互斥锁
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
static int global=0;
pthread_mutex_t mutex;
void *do_thread(void *arg){
int loops=*(int *)arg;
int i,temp=0;
for(i=0;i<loops;i++){
pthread_mutex_lock(&mutex);
temp=global;
temp++;
global=temp;
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc,char *argv[]){
pthread_mutex_init(&mutex,NULL);
pthread_t thread_id[2]={0};
int err;
int loops=atoi(argv[1]);
for(int i=0;i<2;i++){
err=pthread_create(&thread_id[i],NULL,do_thread,(void *)&loops);
if(err!=0){
perror("[ERROR] pthread_create():");
exit(EXIT_FAILURE);
}
}
pthread_join(thread_id[0],NULL);
pthread_join(thread_id[1],NULL);
printf("%d\n",global);
pthread_mutex_destroy(&mutex);
return 0;
}
线程同步
线程同步: 是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问
示例:基于互斥锁实现生产者与消费者模型(主线程为消费者,n 个子线程作为生产者)
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<unistd.h>
#include<pthread.h>
static int numbers=0;
static int total_of_produce=0;
static int total_of_consume=0;
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
void *thread_handler(void *arg){
int cnt=atoi((char *)arg);
for(int i=0;i<cnt;i++){
pthread_mutex_lock(&mtx);
printf("Producer[%ld] product a produce, numbers=%d\n",pthread_self(),++numbers);
pthread_mutex_unlock(&mtx);
}
pthread_exit((void *)0);
}
int main(int argc,char *argv[]){
pthread_t tid;
int err;
for(int i=1;i<argc;i++){
total_of_produce +=atoi(argv[i]);
err=pthread_create(&tid,NULL,thread_handler,(void *)argv[i]);
if(err!=0){
perror("[ERROR] pthread_create:");
exit(EXIT_FAILURE);
}
}
for(;;){
pthread_mutex_lock(&mtx);
while(numbers>0){//如果仓库有产品,则消费
total_of_consume++;
printf("consume a product! numbers=%d\n",--numbers);
sleep(1);
}
pthread_mutex_unlock(&mtx);
if(total_of_consume>=total_of_produce) break;
}
return 0;
}
条件变量
条件变量代表是一个通讯机制,用于传递通知与等待通知, 用户可以设定条件来发送或者等待通知
初始化
条件变量的初始化分为 静态初始化 与动态初始化
1、静态初始化
pthread_cond_t cond = PTHREAD_COND_INITALIZER;
2、动态初始化
pthread_cond_init 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
函数功能:初始化条件变量
函数参数
- cond : 条件变量指针
- attr : 条件变量属性
函数返回值
- 成功 : 返回 0
- 失败 : 返回错误码
pthread_cond_destroy函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_destroy(pthread_cond_t *cond);
函数功能:销毁条件变量
函数参数
- cond : 条件变量指针
函数返回值
- 成功 : 返回 0
- 失败 : 返回错误码
pthread_cond_broadcast函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_broadcast(pthread_cond_t *cond);
函数功能:唤醒所有阻塞在某个条件变量上的线程
函数参数
- cond : 条件变量指针
函数返回值
- 成功: 返回 0
- 失败: 返回 错误码
pthread_cond_signal
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
函数功能:唤醒睡眠的线程,一次只能唤醒一个线程
函数返回值
- 成功: 返回 0
- 失败: 返回 错误码
pthread_cond_wait函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mtx);
函数功能:用于阻塞当前线程,等待别的线程使用pthread_cond_signal()
或pthread_cond_broadcast
来唤醒它
示例:基于条件变量实现生产者与消费者模型 (多个生产者对应一个消费者)
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<unistd.h>
#include<pthread.h>
static int numbers=0;
static int total_of_produce=0;
static int total_of_consume=0;
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond =PTHREAD_COND_INITIALIZER;
void *thread_handler(void *arg){
int cnt=atoi((char *)arg);
for(int i=0;i<cnt;i++){
pthread_mutex_lock(&mtx);
printf("Producer[%ld] product a produce, numbers=%d\n",pthread_self(),++numbers);
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
}
pthread_exit((void *)0);
}
int main(int argc,char *argv[]){
pthread_t tid;
int err;
for(int i=1;i<argc;i++){
total_of_produce +=atoi(argv[i]);
err=pthread_create(&tid,NULL,thread_handler,(void *)argv[i]);
if(err!=0){
perror("[ERROR] pthread_create:");
exit(EXIT_FAILURE);
}
}
for(;;){
pthread_mutex_lock(&mtx);
while(numbers==0){
pthread_cond_wait(&cond,&mtx);
}
while(numbers>0){
total_of_consume++;
printf("consume a product! numbers=%d\n",--numbers);
sleep(1);
}
pthread_mutex_unlock(&mtx);
if(total_of_consume>=total_of_produce) break;
}
return 0;
}