Linux之多线程

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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值