文章目录
- 一、线程(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. 使用(单个参数)
4. 使用(多个参数)
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 <pthread.h>
//静态初始化
pthread_mutex_t lock;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//静态初始化
//动态初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);功能:动态初始化互斥锁参数:mutex:要初始化的锁mutexattr:属性 一般传 NULL 默认属性返回值:总是会成功 返回0
(二)上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:上锁(如果没有锁可以上锁,线程将被阻塞)
参数:mutex 互斥锁指针
返回值:成功 0 失败 错误码int pthread_mutex_trylock(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 失败 错误码