文章目录
- 一、线程(LWP)概念
- 二、线程相关函数
- (一)创建 pthread_create
- 1. 定义
- 2. 使用(不传参)
- 3. 使用(单个参数)
- 4. 使用(多个参数)
- 5. 多线程执行的顺序
- 6. 多线程内存空间
- (二)获取线程号 pthread_self
- (三)退出线程 pthread_exit
- (四)线程 pthread_join
- (五)标记分离态 pthread_detach
- (六)取消线程pthread_cancel
- (七)多线程文件拷贝
- 进阶版:线程数由命令行传参
一、线程(LWP)概念
轻量级的进程,进程是资源分配的基本单位,线程是系统调度的基本单位。
线程不会分配内存空间,一个进程中的多个线程是共用进程的内存空间的(0-3G)
多线程没有多进程安全,但是多线程的效率更高。
每个线程都有自己的task_struct
结构体,每个线程都独立的参与时间片轮转
多线程的函数是第三方库实现的。
编码时需要加头文件 #include <pthread.h>
编译时 需要链接 线程库 -lpthread
线程之间是相互平等的
二、线程相关函数
(一)创建 pthread_create
1. 定义
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:
thread:线程id tid
attr:线程属性 NULL 表示默认属性
start_routine:线程体(线程处理函数)
arg:给线程处理函数传参的 如果没有参数可传 可以置NULL
返回值:
成功 0
失败 错误码
- 注:
- pthread_t是一个long类型的数
- start_routine是返回值和参数都是void*的函数指针
- 主线程结束,整个进程结束
2. 使用(不传参)
#include <my_head.h>
//线程处理函数
void *task_func(void *arg){
printf("我是子线程\n");
}
int main(int argc, const char *argv[])
{
pthread_t tid = 0;
int ret = 0;
if(0 != (ret = pthread_create(&tid, NULL, task_func, NULL))){
printf("pthread_create : %s\n", strerror(ret));
exit(-1);
}
printf("我是主线程\n");
return 0;
}
- 注:在不传参时,最后一个参数可以置NULL
3. 使用(单个参数)
#include <my_head.h>
void *print_num(void* num){
#if 0
//方式1:此处定义一个指针
int *p=(int *)num;
printf("num = %d\n",*p);//此种方式在多线程时指向的是同一块内存空间
#else
//方式2:此处定义一个变量
int p=* (int*) num;
printf("num = %d\n",p);//此种方式在多线程时指向的是各自的局部变量
#endif
}
int main(int argc, char const *argv[])
{
pthread_t tid=0;
int num=0;
printf("please input a number:");
scanf("%d",&num);
pthread_create(&tid,NULL,print_num,&num);
pthread_join(tid,NULL);
return 0;
}
- 注:
- 线程处理函数种中是以void*的类型传进函数中的,所以在使用之前需要进行强制类型转换。
关于:int p=* (int*) num;
和int p=(int)(*num)
的区别。
在上述例子中使用int p=(int)(*num)
会报错。
因为num是void*类型的指针,上述警告就是说明正在对一个void*类型的指针进行解引用;而错误是因为强制类型转换的表达式是一个void表达式。所以不能使用这种方式。
4. 使用(多个参数)
#include <my_head.h>
typedef struct _data{
int a;
int b;
char c;
}data_t;
void *print_num(void* num){
#if 1
//方式1:此处定义一个指针
data_t *p=(data_t *)num;
printf("a = %d;b = %d;c = %c\n",p->a,p->b,p->c);//此种方式在多线程时指向的是同一块内存空间
#else
//方式2:此处定义一个变量
data_t p=* (data_t *) num;
printf("a = %d;b = %d;c = %c\n",p.a,p.b,p.c);//此种方式在多线程时指向的是各自的局部变量
#endif
}
int main(int argc, char const *argv[])
{
pthread_t tid=0;
data_t data = {1,3,'a'};
pthread_create(&tid,NULL,print_num,&data);
pthread_join(tid,NULL);
return 0;
}
5. 多线程执行的顺序
多线程执行也没有先后顺序,也是时间片轮转,上下文切换
6. 多线程内存空间
同一个进程中的多个线程共用进程内存空间
全局区、堆区、字符串常量区都是公用的
只有栈区是每个线程独立的(作用域问题)
线程之间通信简单,使用全局变量即可,但是不安全
(二)获取线程号 pthread_self
#include <pthread.h>
pthread_t pthread_self(void);
功能:获取调用线程的线程号 tid
参数:无
返回值:总是会成功 返回tid
ps -eLf 此时显示的线程号经过了优化方便人查看,并非实际的线程号
(三)退出线程 pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
功能:退出当前的线程
参数:retval:退出线程时返回的状态值(其他线程用pthread_join来接收)
返回值:无
线程退出的四种情况:
- 线程处理函数执行结束
- 线程中调用pthread_exit退出
- 同一进程中的任一线程中调用exit或者主函数return
- 接收到发送的pthread_cancel取消信号时
(四)线程 pthread_join
结合态线程:结束时必须由其他线程(并没有要求必须创建这个线程的线程来调用)调用pthread_join来回收资源,如果结合态的线程结束后没有回收资源,默认属性是结合态。
分离态线程:结束时由操作系统自动回收其资源
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待指定的结合态的线程结束 为其回收资源
参数:
thread:等待结束的线程号
retval:线程结束时的退出状态值 如果不关心可以传NULL
返回值:
成功 0
失败 错误码
- 注:
- 该函数是阻塞等待
- 第二个参数一般传NULL,不关心返回值
- 注意不能返回局部变量的地址,可以返回全局变量的地址,或者static修饰的局部变量的地址,或者malloc在堆区分配的地址(但是要注意回收资源的线程要free)
(五)标记分离态 pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:标记一个线程为分离态 结束时由操作系统自动回收资源
参数:thread:线程号
返回值:
成功 0
失败 错误码
- 注:在主线程和子线程本身中标记子线程为分离态均可以,但是在子线程中标记自己更好,因为线程执行不分先后顺序,在子线程中标记,可以确保在子线程未执行结束前可以标记成功。
- 一般在子线程开头进行标记,当作是线程属性的一种
(六)取消线程pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
给线程发一个取消的请求
目标线程是否以及何时响应这个请求取决于线程的两种属性:statu和type
参数:thread:线程id
返回值:
成功 0
失败 错误码
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
//设置线程是否可被取消
PTHREAD_CANCEL_ENABLE 可被取消 ----默认属性
PTHREAD_CANCEL_DISABLE 不可被取消
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
//设置线程的取消类型
PTHREAD_CANCEL_DEFERRED 延时取消 ----默认属性
直到线程下一次调用能作为取消点的函数时才会被取消
PTHREAD_CANCEL_ASYNCHRONOUS 立即取消
不可被取消,不接收取消信号
可被取消,立即取消,在接收到取消信号后立即取消;
可被取消,延时取消,在接收到取消信号先将当前命令执行完后,如果再次遇到可作为取消点的函数时被取消;如果遇不到就无法取消
- 注:默认是可被取消,延时取消
(七)多线程文件拷贝
功能需求:
使用多线程拷贝文件
代码实现:
//实现多线程拷贝文件
#include <my_head.h>
typedef struct _msg{
char *src_file;
char *dest_file;
int offset;
int len;
}msg_t;
//初始化:保证有一个清空的目标文件,获取源文件的长度
int init_cp(const char *src_file, const char *dest_file){
//打开一个目标文件,不存在就创建,存在就清空
FILE *dest_fp=fopen(dest_file,"w");
if(NULL == dest_fp) ERR_LOG("open dest file error");
fclose(dest_fp);
//获取源文件长度
FILE *src_fp=fopen(src_file,"r");
if(NULL == src_fp) ERR_LOG("open src file error");
fseek(src_fp,0,SEEK_END);
int size = ftell(src_fp);
fclose(src_fp);
return size;
}
//线程处理函数
void *func_cp(void *argv1){
printf("子线程函数:%ld\n",pthread_self());
//使用结构体接收参数
msg_t t_argv=*(msg_t *)argv1;
//打开文件
int src_fd = open(t_argv.src_file,O_RDONLY);
if(-1 == src_fd) ERR_LOG("open src file error");
int dest_fd = open(t_argv.dest_file,O_WRONLY);
if(-1 == dest_fd) ERR_LOG("open dest file error");
//将两个文件的指针移动到offset位置
lseek(src_fd,t_argv.offset,SEEK_SET);
lseek(dest_fd,t_argv.offset,SEEK_SET);
//复制函数
int w_byte=0;//记录写入的字节数
int r_byte=0;//记录本次读到的字节数
char buff[10];//缓冲区
while(0 < (r_byte=read(src_fd,buff,sizeof(buff)))){
w_byte+=r_byte;
if(w_byte>=t_argv.len){
write(dest_fd,buff,t_argv.len-(w_byte-r_byte));
break;
}
write(dest_fd,buff,r_byte);
}
//自己就结束了
}
int main(int argc, char const *argv[])
{
if(3 != argc){
printf("Usage:%s src dest\n",argv[0]);
exit(-1);
}
//初始化
int size = init_cp(argv[1],argv[2]);
char src_path[20]={0};
char dest_path[20]={0};
strcpy(src_path,argv[1]);
strcpy(dest_path,argv[2]);
//创建线程
msg_t argv2={src_path,dest_path,0,size/2};
pthread_t tid1=0;
int sta=0;
if(sta = pthread_create(&tid1,NULL,func_cp,(void *)&argv2))
{
printf("pthread_create error:%s\n",strerror(sta));
}
msg_t argv1={src_path,dest_path,size/2,size-size/2};
pthread_t tid2=0;
if(sta = pthread_create(&tid2,NULL,func_cp,(void *)&argv1))
{
printf("pthread_create error:%s\n",strerror(sta));
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
进阶版:线程数由命令行传参
//实现多线程拷贝文件
#include <my_head.h>
typedef struct _msg{
char src_file[20];
char dest_file[20];
int offset;
int len;
}msg_t;
//初始化:保证有一个清空的目标文件,获取源文件的长度
int init_cp(const char *src_file, const char *dest_file){
//打开一个目标文件,不存在就创建,存在就清空
FILE *dest_fp=fopen(dest_file,"w");
if(NULL == dest_fp) ERR_LOG("open dest file error");
fclose(dest_fp);
//获取源文件长度
FILE *src_fp=fopen(src_file,"r");
if(NULL == src_fp) ERR_LOG("open src file error");
fseek(src_fp,0,SEEK_END);
int size = ftell(src_fp);
fclose(src_fp);
return size;
}
//线程处理函数
void *func_cp(void *argv1){
//printf("子线程函数:%ld\n",pthread_self());
//使用结构体接收参数
msg_t t_argv=*(msg_t *)argv1;
//打开文件
int src_fd = open(t_argv.src_file,O_RDONLY);
if(-1 == src_fd) ERR_LOG("open src file error");
int dest_fd = open(t_argv.dest_file,O_WRONLY);
if(-1 == dest_fd) ERR_LOG("open dest file error");
//将两个文件的指针移动到offset位置
lseek(src_fd,t_argv.offset,SEEK_SET);
lseek(dest_fd,t_argv.offset,SEEK_SET);
//复制函数
int w_byte=0;//记录写入的字节数
int r_byte=0;//记录本次读到的字节数
char buff[10];//缓冲区
while(0 < (r_byte=read(src_fd,buff,sizeof(buff)))){
w_byte+=r_byte;
if(w_byte>=t_argv.len){
write(dest_fd,buff,t_argv.len-(w_byte-r_byte));
break;
}
write(dest_fd,buff,r_byte);
}
//自己就结束了
}
int main(int argc, char const *argv[])
{
if(4 != argc){
printf("Usage:%s src_file dest_file thread_num\n",argv[0]);
exit(-1);
}
//初始化
int size = init_cp(argv[1],argv[2]);
//定义参数接一下命令行参数
char src_path[20]={0};
char dest_path[20]={0};
strcpy(src_path,argv[1]);
strcpy(dest_path,argv[2]);
int thread_num=atoi(argv[3]);
int sta=0;//记录创建进程状态
int nbytes=0;
msg_t *p_argv=(msg_t *)malloc(sizeof(msg_t) * thread_num); //参数数组
if(NULL == p_argv){
printf("分配参数空间失败\n");
exit(-1);
}
pthread_t *tid=(pthread_t *)malloc(sizeof(pthread_t) * thread_num);//存放tid
if(NULL == tid){
printf("分配tid空间失败\n");
exit(-1);
}
//创建线程
for(int i=0;i<thread_num;i++){
strcpy(p_argv[i].src_file,src_path);
strcpy(p_argv[i].dest_file,dest_path);
p_argv[i].offset=size/thread_num*i;
if(i == thread_num-1){
p_argv[i].len=size-nbytes;
}
else{
p_argv[i].len=size/thread_num;
}
nbytes+=p_argv[i].len;
if(sta = pthread_create(tid+i,NULL,func_cp,(void *)(p_argv+i)))
{
printf("pthread_create error:%s\n",strerror(sta));
}
}
for(int i=0;i<thread_num;i++){
pthread_join(tid[i],NULL);
}
free(p_argv);
free(tid);
return 0;
}