05-线程
文章目录
一、linux最小资源单位 – 线程
1.线程与进程
进程: 进程是操作系统资源分配的基本实体
线程: 线程是CPU调度的基本单位,是进程内部资源,是linux中最小资源单位。(填空选择)
int main(int argc,char argv[])
{
//单进程
/ 进程开始 */
…;
…; -> 在进程运行过程中,可以创建出多个线程。
…;
/* 进程结束 */
return 0;
}
假设linux是一个社会,进程就是一个车间,线程就是车间里面的各个工人。
2.线程函数接口特点?
1)由于线程函数接口都是封装在一个线程库,所以我们是看不到源码的,查看线程的函数,都是在第3手册: man 3 xxxx
2)所有线程函数的头文件:#include <pthread.h>
二、线程的函数接口
1.创建一个子线程 -> pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
函数参数:
thread:储存线程ID号的变量的地址 pthread_t -> 线程ID号的数据类型
attr:线程的属性,普通属性填NULL
start_routine:线程的执行函数。该线程需要做什么任务,就写到这个函数中函数必须是:void *fun(void *arg) 回调函数–>函数指针
arg:传递给子线程的参数(任意指针类型)
返回值:
成功:0
失败:非0错误码
注意:Compile and link with -pthread. 只要你的程序设计到线程的函数,在编译时必须链接线程库
编译命令: gcc xxx.c -o xxx -lpthread(编译线程的时候一定要加线程库)
说明:
1.在子线程理论上可以创建无数条子线程
2.主线程与子线程是并发运行,没有时间先后之分
3.主线程结束表示整个进程结束;包括子线程也结束。
- 函数指针与回调函数
void *(*start_routine) (void *)
它是一个函数指针类型,指针指向的是一个函数,函数的返回值是void *;函数的参数是void *
typedef void (*sighandler_t)(int); //函数指针
自定义了一个函数指针类型,指针指向一个函数,函数的返回值是void;函数的参数是int;
给这个函数指针类型取了一个别名,名字叫做sighandler_t
代码解释:
#include <stdio.h>
int fun(int a, int b)
{
printf("a+b=%d\n", a + b);
}
/*
__LINE__:行号
__FUNCTION__:函数名
*/
void *fun2(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
return NULL;
}
// 给自定义的函数指针类型取了一个别名 名字叫做bling
typedef int (*bling1)(int, int);
int main()
{
/*
//1.函数指针
//2.指针指向的是一个函数
//3.函数的参数是两个int类型
//4.参数的返回值是一个int
*/
// 给函数指针定义+初始化
int (*bling)(int, int) = fun; // 常用的写法
// int (*bling)(int, int) = &fun;
// 先定义再初始化
// int (*bling)(int, int);
// bling = fun;
// 调用函数
// fun(3,4); //常规直接调用
// 通过函数指针来调用函数
bling(5, 6); // 常用的写法
// (*bling)(5,6); //等价于(*&fun)(5,6)==fun(5,6)
/*
通过别名来定义函数指针变量
*/
bling1 b1 = fun; // 常用写法
b1(7, 8);
// 非常用写法
// bling1 b1 = &fun;
// (*b1)(7,8);
/*
这种类型的函数指针
void *(*bling2)(void *)
*/
void *(*bling2)(void *) = fun2;
// (void *)(*bling2)(void *) = fun2; //不要给返回值加括号 错误
bling2(NULL);
return 0;
}
2)线程同时干不同的事
例子: 尝试创建一个新的线程,看看能不能同时做两件事情
#include<stdio.h>
#include<pthread.h>
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
while(1)
{
sleep(1);
printf("start_routine\n");
}
}
int main()
{
//创建一条线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
while(1)
{
sleep(1);
printf("main\n");
}
return 0;
}
验证一个进程最多能创建几条线程:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
/*
单进程
*/
//子线程start_routine1
void *start_routine1(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
while(1)
{
printf("我爱坤坤.....\n");
sleep(1);
}
}
//子线程start_routine2
void *start_routine2(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
while(1)
{
printf("我是ikun.....\n");
sleep(1);
}
}
int main()
{
/*
主线程
*/
//创建一个子线程
int ret = 0;
pthread_t tid; //不要定义成pthread_t *tid
ret = pthread_create(&tid,NULL,start_routine1,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
//创建一个子线程
pthread_t tid1; //不要定义成pthread_t *tid
ret = pthread_create(&tid1,NULL,start_routine2,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
//普通的函数调用
// start_routine2(NULL);
// pause();
while(1)
{
printf("坤坤打篮球.....\n");
sleep(1);
}
return 0;
}
3)线程的传参
struct st
{
char name[20];
char sex;
int height;
float score;
}
使用线程将一个学生信息传递给子线程,然后在子线程中打印该学生的信息。
代码说明:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 子线程fun1 ---char类型
void *fun1(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
// 拿传参的值
// 1.类型转换
// 2.解引用
char c = *((char *)arg);
printf("arg char:%c\n", c);
}
// 子线程fun2 ---- int类型
void *fun2(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
// 拿传参的值
// 1.类型转换
// 2.解引用
int num = *(int *)arg;
printf("arg int:%d\n", num);
}
// 子线程fun3--- int *指针
void *fun3(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
// 拿传参的值
// 1.类型转换
// 2.解引用
int num = *(int *)arg;
printf("arg int *:%d\n", num);
}
// 子线程fun4----char * 字符串
void *fun4(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
// 拿传参的值
// 1.类型转换
// 2.解引用
printf("arg char *:%s\n", (char *)arg);
}
// 子线程fun5-----int arr[2]
void *fun5(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
// 拿传参的值
// 1.类型转换
// 2.解引用
int *arr = (int *)arg;
// printf("arg int [2]:%d %d\n",*arr,*(arr+1));
printf("arg int [2]:%d %d\n", arr[0], arr[1]);
}
int main()
{
int ret = 0;
/*
传参 :char类型
*/
char c = 'a';
pthread_t tid1;
//&c--> char * 类型转换
ret = pthread_create(&tid1, NULL, fun1, (void *)&c); // 刚开始的时候建议这么写
// ret = pthread_create(&tid1,NULL,fun1,&c); //char *是void *类型中一种
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
/*
传参 :int类型
*/
int num = 250;
pthread_t tid2;
//&num--> int * 类型转换
ret = pthread_create(&tid2, NULL, fun2, (void *)&num); // 刚开始的时候建议这么写
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
/*
传参 :指针类型
*/
int val = 300;
// int *p = &val; //指针p获得是栈空间
int *p = malloc(sizeof(int)); // 堆空间
*p = 350;
pthread_t tid3;
ret = pthread_create(&tid3, NULL, fun3, (void *)p); // 刚开始的时候建议这么写
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
/*
传参 :字符串类型 char *
*/
char *str = "合班6 牛逼";
pthread_t tid4;
ret = pthread_create(&tid4, NULL, fun4, (void *)str);
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
/*
传参:数组
*/
int arr[2] = {3, 4};
pthread_t tid5;
ret = pthread_create(&tid5, NULL, fun5, (void *)arr);
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
// while (1);
pthread_jpin(tid1, NULL);
pthread_jpin(tid2, NULL);
pthread_jpin(tid3, NULL);
pthread_jpin(tid4, NULL);
pthread_jpin(tid5, NULL);
return 0;
}
2、接合一条线程 (阻塞等待子线程的退出) -> pthread_join()
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
thread:需要接合的线程的ID号
retval:储存子线程的退出值指针,如果填NULL,代表不关心子线程的退出状态。
返回值:
成功:0
失败:非0
例子:
情况一:
主线程工作时间长,才去join()。 -> pthread_join()会马上返回。
子线程工作时间短,早就结束。
情况二:
主线程工作时间短,早去join()。 -> pthread_join()会阻塞等待子线程的退出。
子线程工作时间长。
代码参考:
#include<stdio.h>
#include<pthread.h>
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
sleep(5);
printf("start_routine\n");
}
int main()
{
//创建一条线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
//sleep(1);
printf("main\n");
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
说明:
printf(“[%s][%d]num = %d\n”,FUNCTION,LINE,*((int *)arg));
利用__FUNCTION__和__LINE__可以区分子线程和主线程的打印
代码说明:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
/*
单进程
*/
//子线程start_routine1
void *start_routine1(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
int cnt = 10;
while(cnt--)
{
printf("我爱坤坤.....%d\n",cnt);
sleep(1);
}
}
int main()
{
/*
主线程
*/
//创建一个子线程
int ret = 0;
pthread_t tid; //不要定义成pthread_t *tid
ret = pthread_create(&tid,NULL,start_routine1,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
int cnt = 5;
while(cnt--)
{
printf("我是ikun.....%d\n",cnt);
sleep(1);
}
//主线程结合子线程(阻塞等待子线程的退出)
ret = pthread_join(tid,NULL); //类比wait(NULL)
if(ret != 0)
{
perror("pthread_join fail");
}
perror("pthread_join success");
return 0;
}
3、退出线程 -> pthread_exit()
#include <pthread.h>
void pthread_exit(void *retval);
函数作用:
子线程主动结束退出
参数:
retval:子线程退出值变量的地址 -> 这个退出值必须是全局变量(或者是堆)
说明:
不能将遗言存放到线程的局部变量里,因为如果用户写的线程函数退出了,
线程函数栈上的局部变量可能就不复存在了。(函数结束栈空间)
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
int pthread_status = 10;//线程的退出变量 必须存在数据段中
//线程例程
void* start_routine(void*arg)
{
/* int cnt=10;
while(cnt--)
{
sleep(1);
printf("start_routine arg:%d cnt:%d\n",*((int*)arg),cnt);
} */
printf("start_routine\n");
//子线程主动退出
pthread_exit(&pthread_status);
printf("3333\n");
}
int main()
{
//只有一条(PCB结构体只有一个)线程的主进程
//主线程
printf("main start.....\n");
int val = 100;
int ret;
//创建一条子线程(申请一个内核 PCB任务结构体),执行任务(线程例程,说白了创建这条线程之后,去执行一个函数)
pthread_t thread;
ret = pthread_create(&thread,NULL,start_routine, &val);
if(ret != 0){
printf("pthread_create error\n");
}
/* int cnt=20;
while(cnt--){
sleep(1);
printf("主线程 正在执行任务%d....\n",cnt);
} */
//阻塞等待子进程的退出
pthread_join(thread,NULL);
void *retval = NULL; //void *retval = &pthread_status;//重点写法
pthread_join(thread,&retval); //pthread_join(pthread_tthread,void**retval);
printf("阻塞等待子线程[%lu]的退出 成功 退出值为:%d\n",thread,*(int*)retval);
//进程结束
printf("main end.......\n");
return 0;
}
导致线程退出的因素:
The new thread terminates in one of the following ways:
//以下这几种情况之一都可以导致线程退出
* It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls pthread_join(3).
//1)当线程调用了pthread_exit(),还可以将退出值返回给那个接合的它的线程。
* It returns from start_routine(). This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
//2)当例程函数返回时,也可以导致线程的退出,return后面的值就是退出值。
* It is canceled (see pthread_cancel(3)).
//3)收到取消请求。
* Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.
//4)任意一个线程调用exit(),都会导致所有的线程都退出/main函数返回
代码说明:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/*
单进程
*/
int val = 250; // 全局变量---数据段中 ok
// 子线程start_routine1
void *start_routine1(void *arg)
{
// int val = 250; //占空间不ok
// int *p = malloc(4); //堆空间也ok
// *p = 250;
// pthread_exit((void *)p);
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
int cnt = 1;
while (cnt--)
{
printf("我爱坤坤.....%d\n", cnt);
sleep(1);
}
// 线程的退出(如果不需要子线程的退出值,可以不写)
pthread_exit(NULL); //常用写法
// 退出的全局变量
// pthread_exit((void *)&val); //&val-->int *
// 子线程退出一个自己的值"886"
//"886"字符串常量--->.raodata----->数据段
//pthread_exit((void *)"886"); // char *类型
// 子线程退出一个自己的值"1" 模拟return 1的写法
//"1"字符串常量--->.raodata----->数据段
// pthread_exit((void *)"1"); // char *类型
// exit(0); //慎用
}
int main()
{
/*
主线程
*/
// 创建一个子线程
int ret = 0;
pthread_t tid; // 不要定义成pthread_t *tid
ret = pthread_create(&tid, NULL, start_routine1, NULL);
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
int cnt = 1;
while (cnt--)
{
printf("我是ikun.....%d\n", cnt);
sleep(1);
}
// 主线程结合子线程不关心子线程的退出值(阻塞等待子线程的退出)
// ret = pthread_join(tid,NULL); //类比wait(NULL) 常用写法
// if(ret != 0)
// {
// perror("pthread_join fail");
// }
// perror("pthread_join success");
// int pthread_join(pthread_t thread, void **retval);
// 主线程结合子线程关心子线程的退出值(阻塞等待子线程的退出)
void *retval = NULL; // 不要定义成void **retval
//&retval的类型就是void **类型
ret = pthread_join(tid, &retval); // retval指针是通过函数pthread_join给它空间
if (ret != 0)
{
perror("pthread_join fail");
}
// 此处接收的是全变变量 int val=250
printf("pthread_join success retval:%d\n",*(int*)retval); //如果retval没有空间的一定会段错误(段错误的原因就是访问空指针)
// 此处接收的是常量"886"
// printf("pthread_join success retval:%s\n", (char *)retval);
//此处接收的是常量"1"
// int ret1 = atoi((char *)retval);
// printf("pthread_join success retval:%d\n", atoi((char *)retval) );
// if(ret1 == 1)
// {
// printf("ok\n");
// }
// if(strcmp((char *)retval,"1") == 0)
// {
// printf("ook\n");
// }
return 0;
}
三、主线程与子线程之间的通信方式
主线程与子线程之间互相通信有哪几种方式:
1.主线程通过参数arg来给子线程发送数据
2.子线程用pthread_exit()退出,主线程用pthread_join()来接受子线程的值
3.全局变量
代码说明:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/*
单进程
*/
int val = 250; // 全局变量---数据段中 ok
// 子线程start_routine1
void *start_routine1(void *arg)
{
printf("[%d][%s]\n", __LINE__, __FUNCTION__);
int num = *(int *)arg;
// 子线程退出一个自己的值"1" 模拟return 1的写法
//"1"字符串常量--->.raodata----->数据段
pthread_exit((void *)"1"); // char *类型
}
int main()
{
/*
主线程
*/
// 创建一个子线程
int num = 10;
int ret = 0;
pthread_t tid; // 不要定义成pthread_t *tid
ret = pthread_create(&tid, NULL, start_routine1, (void *)&num);
if (ret != 0)
{
perror("pthread_create fail");
return -1;
}
//结合子线程
void *retval = NULL; // 不要定义成void **retval
//&retval的类型就是void **类型
ret = pthread_join(tid, &retval); // retval指针是通过函数pthread_join给它空间
if (ret != 0)
{
perror("pthread_join fail");
}
// 此处接收的是全变变量 int val=250
printf("pthread_join success retval:%d\n",*(int*)retval); //如果retval没有空间的一定会段错误(段错误的原因就是访问空指针)
return 0;
}
总结
没有总结…下篇继续…