为什么要学习线程呢?之前我们不是学了进程吗?通过创建进程一样可以实现多个程序的并发运行。那么我们学习线程的意义在于什么?
意义:
假设,在我们完成相关任务的不同代码间需要交换数据时候。
如果采用多进程并发方式:
①进程创建比线程创建花费时间多
②进程之间通信比较麻烦
采用多线程方式:
①共享全局变量,线程间数据交换会比较高效。
线程:是操作系统能够进行运算调度的最小单位,是进程的一条执行路径,在Unix系统下也被称为轻量级进程(轻量进程更多指内核线程,而把用户线程称为线程)。所有的线程都是在同一进程空间运行,这也意味着多条线程将共享该进程中的全部系统资源。
多线程:一个进程可以有很多线程,每条线程并行执行不同的任务。
就像我们多进程一样,我们有父进程,有子进程。那么多线程也一样,有主线程,有子线程等。
主线程:一个进程创建后,会首先生成一个缺省的线程,称为主线程。
子线程:由主线程调用pthread_create()创建的线程。
特点:
①有自己的入口函数,该函数由用户在创建的时候指定。
②每个线程都有自己的线程ID,可以通过pthread_self()函数获取。
主线程和子线程的默认关系:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。
产生的问题:
①当进程结束或僵死后,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。(这就像工厂要关门了,结果发现里面还有工人,他们还没有完成任务。这时工厂如果要关门,只有把它们赶走后才能关门)
②线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。(这就像工厂要关门了,催促工人赶紧走,但是有些工人急急忙忙走后,但运行的机器没有被关闭)
所以,我们需要提前设置线程属性去解决这些问题。
可会合:主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。(简单来说:就是上面提到的,工厂需要确保工人和机器都关闭了再关门)
相分离:表示子线程无需和主线程会合,也就是相分离的。这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。(简答来说:工厂虽然关门了,但是让工人们完成自己任务后,自己从后门出去(释放系统资源))
pthread_create()函数:创建一个线程。
#include //头文件包含
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void*arg);
参数 | 功能 |
---|---|
*thread | 指向线程标识符的指针 (返回线程ID) |
pthread_attr_t*attr | 设置线程属性。 |
start_routine | 线程运行函数的起始地址 |
arg | 运行函数的参数 |
详解:void *(*start_routine) (void *)
定义函数指针: start_routine
返回值:void *(一个指针)
参数:void *(一个指针)
pthread_attr_t定义:
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
pthread_attr_init ()函数:初始化一个线程对象的属性。
注意:一般调用了pthread_attr_init()函数初始化线程属性后,要用pthread_attr_destroy()函数对其去初始化
int pthread_attr_init(pthread_attr_t *attr);
参数:要初始化线程属性结构的指针
pthread_attr_destroy()函数:销毁一个目标结构,并且使它在重新初始化之前不能重新使用。
int pthread_attr_destroy(pthread_attr_t *attr);
参数:要删除的线程属性结构体指针
int pthread_attr_setstacksize()函数:设置线程堆栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
默认堆栈大小: 8388608; 堆栈最小:16384 。单位:字节
pthread_attr_setdetachstate()函数:设置线程关系是可会合还是相分离(默认:可会合)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置为相分离
pthread_join()函数:等待一个线程的结束,线程间同步的操作。
注意:一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返回ESRCH 错误。
#include //头文件包含
int pthread_join(pthread_t thread, void **retval);
参数 | 功能 |
---|---|
thread | 线程ID |
retval | 用户定义的指针,用来存储被等待线程的返回值 |
示例:
pthread_join(tid, NULL);
pthread_exit()函数:终止调用它的线程并返回一个指向某个对象的指针。
void pthread_exit(void* retval);
pthread_exit()
目的:用简单例子讲解多线程创建基本使用,并且了解子线程共享主线程的数据。
#include
#include
#include
#include
#include
#include
void *thread_worker1(void *args);
void *thread_worker2(void *args);
int main(int argc, char **argv)
{
int shared_var = 1000; //导入线程的参数
pthread_t tid; //线程ID
pthread_attr_t thread_attr; //线程属性
//一、设置线程属性
//初始化线程属性
if(pthread_attr_init(&thread_attr))
{
printf("pthread_attr_init() failure: %s\n", strerror(errno));
return -1;
}
//设置线程堆栈大小
if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
{
printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
return -2;
}
//设置线程关系:分离还是会合 默认是会合,这里设置是分离
if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
{
printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
return -3;
}
//二、创建线程
//线程1
pthread_create(&tid, &thread_attr, thread_worker1, &shared_var); //线程ID 线程属性
printf("Thread worker1 tid[%ld] created ok\n", tid);
//线程2
pthread_create(&tid, NULL, thread_worker2, &shared_var);
printf("Thread worker2 tid[%ld] created ok\n", tid);
//销毁线程
pthread_attr_destroy(&thread_attr);
//等待线程2退出再执行下面代码
pthread_join(tid, NULL);
//主线程工作
while(1)
{
printf("Main/Control thread shared_var: %d\n", shared_var);
sleep(10);
}
}
简析:
①调用pthread_attr_destroy():对其去除初始化(pthread_attr_init)
②调用pthread_join():因为worker1设置了相分离的关系,所以并不会返回主线程。所以,这里pthread_join()函数等待的是worker2结束。
void *thread_worker1(void *args)
{
int *ptr = (int *)args;
if( !args )
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL); //若用exit会退出进程
}
printf("Thread workder 1 [%ld] start running...\n", pthread_self());
//子线程工作
while(1)
{
printf("+++: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
*ptr += 1;
sleep(2);
printf("+++: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
}
printf("Thread workder 1 exit...\n");
return NULL;
}
void *thread_worker2(void *args)
{
int *ptr = (int *)args;
if( !args )
{
printf("%s() get invalid arguments\n", __FUNCTION__);
pthread_exit(NULL);
}
//子线程工作
printf("Thread workder 2 [%ld] start running...\n", pthread_self());
while(1)
{
printf("---: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
*ptr += 1;
sleep(2);
printf("---: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
}
printf("Thread workder 2 exit...\n");
return NULL;
}
简析:
①这里__FUNCTION__作用是:获取函数名。
gcc thread.c -o thread -lpthread
./thread