目录
1、线程
线程实际上是应用层的概念,在Linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源,如下图所示。
上图中:
- 左边是一个含有单个线程的进程,它拥有自己的一套完整的资源。
- 右边是一个含有两条线程的进程,线程彼此间共享进程内的资源。
由此可见,线程是一种轻量级进程,提供一种高效的任务处理方式
2、创建线程
创建线程的函数接口说明如下:
创建一个子线程? -> pthread_create() -> man 3 pthread_create
pthread_t tid1;
#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(编译线程的时候一定要加线程库)
理论上按64位系统的虚拟内存大小,理论上可以创建无数个线程。
事实上,肯定创建不了那么多线程,除了虚拟内存的限制,还有系统的限制。
3、线程传参
利用pthread_create函数的第四个参数来传参
1)int类型数据 先将arg强制转换为int*类型,再解引用
2)char类型数据 先类型转换,再解引用
3)char*(字符串)类型数据 直接类型转换,不需要解引用
4)指针类型数据 直接类型转换,不需要解引用
5)结构体两种方式的数据 结构体对象需要类型转换和解引用,结构体指针只需要类型转换,不需要解引用
线程传参的测试代码如下::
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
struct student
{
char name[20];
char sex;
int height;
float scores;
};
//1、int
void* fun1(void* arg)
{
int a = *(int*)arg;
while(1)
{
printf("[%d]%s 传参为a = %d\n",__LINE__,__FUNCTION__,a);
sleep(1);
}
}
//2、char
void* fun2(void* arg)
{
char c = *(char *)arg;
while(1)
{
printf("[%d]%s 传参为c = %c\n",__LINE__,__FUNCTION__,c);
sleep(1);
}
}
//3、char*
void* fun3(void* arg)
{
while(1)
{
printf("[%d]%s 传参为s = %s\n",__LINE__,__FUNCTION__,(char *)arg);
sleep(1);
}
}
//4、指针
void* fun4(void* arg)
{
int* p = (int *)arg;
while(1)
{
printf("[%d]%s 传参为p = %d\n",__LINE__,__FUNCTION__,*p);
sleep(1);
}
}
//5、结构体对象
void* fun5(void* arg)
{
struct student s1 = *(struct student*)arg;
while(1)
{
printf("[%d]%s 传参为s1 = %s %c %d %f\n",__LINE__,__FUNCTION__,s1.name,s1.sex,s1.height,s1.scores);
sleep(1);
}
}
//6、结构体指针
void* fun6(void* arg)
{
struct student* s2 = (struct student*)arg;
while(1)
{
printf("[%d]%s 传参为s2 = %s %c %d %f\n",__LINE__,__FUNCTION__,s2->name,s2->sex,s2->height,s2->scores);
sleep(1);
}
}
int main()
{
int a = 250;
char c = 'a';
char* s = "hello world";
int* p = &a;
struct student s1 = { "sakura0908",'b',180,90.5};
struct student* s2 = &s1;
int ret = 0;
pthread_t tid1;
ret = pthread_create(&tid1,NULL,fun1,(void*)&a);
if(ret != 0)
{
printf("pthread_create1 fail\n");
return -1;
}
//printf("pthread_create1 ok\n");
pthread_t tid2;
ret = pthread_create(&tid2,NULL,fun2,(void*)&c);
if(ret != 0)
{
printf("pthread_create2 fail\n");
return -1;
}
//printf("pthread_create2 ok\n");
pthread_t tid3;
ret = pthread_create(&tid3,NULL,fun3,s);
if(ret != 0)
{
printf("pthread_create3 fail\n");
return -1;
}
//printf("pthread_create3 ok\n");
pthread_t tid4;
ret = pthread_create(&tid4,NULL,fun4,p);
if(ret != 0)
{
printf("pthread_create4 fail\n");
return -1;
}
//printf("pthread_create4 ok\n");
pthread_t tid5;
ret = pthread_create(&tid5,NULL,fun5,(void*)&s1);
if(ret != 0)
{
printf("pthread_create5 fail\n");
return -1;
}
//printf("pthread_create5 ok\n");
pthread_t tid6;
ret = pthread_create(&tid6,NULL,fun6,s2);
if(ret != 0)
{
printf("pthread_create6 fail\n");
return -1;
}
//printf("pthread_create6 ok\n");
while(1)
{
//printf("[%d]%s\n",__LINE__,__FUNCTION__);
sleep(1);
}
return 0;
}
4、子线程与主线程通信方式
主线程与子线程之间互相通信有哪几种方式:
1.主线程通过参数arg来给子线程发送数据(常用)
2.子线程用pthread_exit()退出,主线程用pthread_join()来接受子线程的值
3.全局变量(常用)
5、线程接合与退出
与进程类似,线程退出之后不会立即释放其所占有的系统资源,而会成为一个僵尸线程。其他线程可使用 pthread_join() 来释放僵尸线程的资源,并可获得其退出时返回的退出值。
包括主线程在内,所有线程的地位是平等的,任何线程都可以先退出,任何线程也可以接合另外一条线程。
接合与退出函数接口说明如下:
pthread_join() -> man 3 pthread_join
功能:阻塞等待子线程的退出
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:thread:需要接合的线程的ID号
retval:储存子线程的退出值指针,如果填NULL,代表不关心子线程的退出状态。
返回值:成功:0
失败:非0
接口说明:
若指定tid的线程尚未退出,那么该函数将持续阻塞。
若只想阻塞等待指定线程tid退出,而不想要其退出值,那么val可置为NULL。
若指定tid的线程处于分离状态,或不存在,则该函数会出错返回。
退出线程 -> pthread_exit() -> man 3 pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
函数作用:子线程主动结束退出
参数:retval:子线程退出值变量的地址 -> 这个退出值必须是全局变量
说明:不能将遗言存放到线程的局部变量里,因为如果用户写的线程函数退出了,线程函数栈上的局部变量可能就不复存在了。
导致线程退出的因素:
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<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int ret = 250;
void* fun1(void* arg)
{
int cnt = 5;
while(cnt--)
{
printf("%d... [%s][%d]\n",cnt,__FUNCTION__,__LINE__);
sleep(1);
}
printf("等待主线程退出...\n");
//pthread_exit("1");
pthread_exit(&ret);
}
int main()
{
int ret = 0;
pthread_t tid1;
ret = pthread_create(&tid1,NULL,fun1,NULL);
if(ret != 0)
{
printf("pthread_create fail\n");
return -1;
}
printf("pthread_create ok\n");
int cnt = 7;
while(cnt--)
{
printf("[%s][%d] [%d]...\n",__FUNCTION__,__LINE__,cnt);
sleep(1);
}
printf("阻塞等待子线程退出\n");
void* retval = NULL;
ret = pthread_join(tid1,&retval);
if(ret != 0)
{
printf("pthread_join fail\n");
return -1;
}
printf("pthread_join ok\n");
//int ret_val = atoi((char *)retval);
int ret_val = *(int*)retval;
printf("接收子线程的值:%d\n",ret_val);
return 0;
}