linux下的线程创建相关API函数(pthread _create、pthread _join、pthread _detach、pthread _concel、pthread _kill)

线程创建相关API函数

pthread _create(创建线程函数)

1. 头文件

#include <pthread.h>

2. 函数原型

POSIX通过pthread_create()函数创建线程,API定义如下:

int  pthread_create((pthread_t  *thread,  pthread_attr_t  *attr,  void  **start_routine)(void  *,  void  *arg)

在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库

3. 参数说明

 >第一个参数为指向线程标识符的指针。
	 - 当创建线程成功,线程ID由第一个参数返回。
 - 第二个参数用来设置线程属性attr
 - 第三个参数是线程运行函数的起始地址。
	 - 第三个参数是线程的入口地址,线程是一个执行路线, 是进程中的单独控制序列,控制序列的入口地址由第三个参数指定。第三个参数是一个函数指针,函数指针所指向的函数特征如下,接收一个无类型的参数,返回的也是无类型指针。
 - 第四个参数是运行函数的参数。
 	- 第四个参数为传递给线程函数的参数,也就是第三个参数所指向函数的参数。尽管arg是**void ***类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,**start_routine()**可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由**pthread_join()**获取。
 	- 如果需要向**start_routine**函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为**arg**的参数传入。
struct fun_para
{
       var para1;//参数1
       var para2;//参数2
       .......
}
//将这个结构体指针,作为void *形参的实际参数传递
struct fun_para para;
pthread_create(&ntid, NULL, thr_fn,&para);
//接着在线程的调用函数thr_fn中可以通过下面的方式使用通过para传入的参数。
void *thr_fn(void *arg)
{
       fun_para *para;
       para = (fun_para *) arg;
       para->para1;//参数1
       para->para2;//参数2    
       ......
       //pthread_exit(0);
       return ((void *)0);
}

4. 返回值:

成功,返回0;出错,返回-1。

5. 举例说明:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
pthread_t t_id;
void printf_tid_pid(const char *s)
{
        pid_t pid;
        pthread_t tid;
        pid = getpid(); //Linux 环境编程中用于定义进程 ID
        tid = pthread_self();//获取线程ID号
        printf("%s pid %u tid %u (0x%x)\n",s,(unsigned)pid,(unsigned)tid,(unsigned)tid);
}
 
void *thread_fuction(void *arg)
{
        printf_tid_pid("new thread: ");
        return ((void*)0);
}
int main(int argc,int argv)
{
        int err;
	t_id = pthread_self();
	printf("%s  %u \n","t_id: ",(unsigned)t_id);
        err = pthread_create(&t_id,NULL,thread_fuction,NULL);
        if(err != 0 )
        {
                printf("create thread fail: %s\n",strerror(err));
        }
        printf_tid_pid("main thread:");
        sleep(1);//注释掉,线程没有执行,程序就终止了,所以需要mian进程需要sleep
        return 0;
}

在这里插入图片描述

6、传递不同参数

传递单个参数:

#include <iostream>
#include <pthread.h>
#include<unistd.h>
using namespace std;

void* thr_fn(void* arg)
{
        int i = *(int*)arg;
        cout << i << endl;

        return ((void*)0);
}

int main()
{
        pthread_t tid;
        int j = 2;
        pthread_create(&tid, NULL, thr_fn, &j);
        sleep(1);
        return 0;
}

在这里插入图片描述
传递多个参数:

#include <iostream>
#include <pthread.h>
#include<unistd.h>
using namespace std;

struct parameter
{
        char a;
        int i;
        float f;
};

void* thr_fn(void* arg)
{
        struct parameter *p =(parameter*)arg;

        cout << p->a << endl;
        cout << p->i << endl;
        cout << p->f << endl;

        return ((void*)0);
}

int main()
{
        pthread_t tid;
        struct parameter *par =  new parameter;
        par->a = 'c';
        par->i = 2;
        par->f = 3.14;
        pthread_create(&tid, NULL, thr_fn, (void*)par);
        sleep(1);
        return 0;
}

在这里插入图片描述

pthread_join()

1. 含义

使一个线程等待另一个线程结束,通过pthread_join函数会让主线程阻塞,直到所有线程都已经退出

2. 背景

如果没有pthread_join;主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

3. 函数原型

 int pthread_join(pthread_t thread, void **value_ptr);
  • 参数说明:

thread:等待退出线程的线程号。
value_ptr:用户定义的指针,它可以用来存储被等待线程的返回值

  • 返回值
    成功:0;失败:错误号 strerror函数

4. 第二个参数说明

void **value_ptr

二级指针其实在函数内部是承接了上个线程的返回值。

在使用时要注意的是:针对value_ptr,应该先声明一个一级指针,然后通过取地址的方式传给pthread_create函数,而不应该直接定义一个二级指针,将这个二级指针直接传递给pthread_create。

正确的传递方法:

void *ret;
pthread_join(thread, &ret);

错误的传递方法:

void **ret;
pthread_join(thread, ret);

原因:
pthread_join中有一句类型这样的语句:

 (* value_ptr) = arg ;

如果按照正确的方式传递参数,左边的语句实际上完成的操作是:
ret = arg;
当按照错误的方式传递参数时,会出现一句致命的错误:(* value_ptr),它对一个还没有初始化的指针进行了取值操作,这是系统所不允许的,此时(* value_ptr)等价于*ret,ret还是一个指针,是一个二级指针,但是ret是一个野指针!
注意啊!你返回的是一个一级指针,那么存储就只能是用 * &ret 来存储(希望你懂,就是指针降级)=ret;那么输出自然也就是 ret了。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

#define N 64

int a = 10;
void * handler(void *arg)
{
	printf("a=%d, %s\n", a, (char *)arg);
	strcat((char *)arg, " ......");
	pthread_exit(arg);
}

int main()
{
	pthread_t tid;
	char buf[N] = {"welcome"};
	void *result;

	if (pthread_create(&tid, NULL, handler, (void *)buf) != 0)
		exit(-1);
	printf("*\n");
	pthread_join(tid, &result);
	printf("ret:%s\n", (char *)result);

	return 0;
}

在这里插入图片描述
如果:

void **result;
pthread_join(tid, result);

在这里插入图片描述

5. 终止状态(线程返回值)

通过pthread_join得到的终止状态是不同的,总结如下:

  • 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值(即void *指针);
  • 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
  • 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数(即void *指针),其实1与3是等效的。
  • 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void *thread_function(void *arg) {
  int i;
  for ( i=0; i<8; i++)
  {
    printf("Thread working...! %d \n",i);
    sleep(1);
  }
  return NULL;
}
int main(void) {
  pthread_t mythread;
  
  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) 
  {
    printf("error creating thread.");
    abort();
  }
  if ( pthread_join ( mythread, NULL ) ) 
  {
    printf("error join thread.");
    abort();
  }
  printf("thread done! \n");
  exit(0);
}

在这里插入图片描述
如果去掉 pthread_join()
在这里插入图片描述

6、回收子线程的返回值

一个线程的结束,有两种方式,一种是正常结束。一种是使用pthread_exit。对于使用pthread_exit结束的线程,可以返回一个"status"给主线程。

void pthread_exit(void* retval);

那么它的参数就和pthread_join第二个参数就对应上了
pthread_exit返回的是一种状态,那么如何获取这个返回的状态?

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>


void* StartFunction(void* arg)
{
    int a =10;
    pthread_exit((void*)a);
}

int main(int argc, char* argv[])
{
    pthread_t my_pthread_t ;
    if (0 != pthread_create(&my_pthread_t, NULL, StartFunction, NULL))
    {
        printf("pthread_create error!\n");
        return -1;
    }
    void* p = NULL;
    if (0 != pthread_join(my_pthread_t, &p))
    {
        printf("pthread_join error!\n");
        return -1;
    }
    printf("ret = %d\n", p);
    return 0;
}

注意

  • 子线程的返回值为void *指针,其指针所指向的地址必须位于全局区或者malloc分配的空间,不能位于用户栈空间;
  • 之所以子线程的返回值以及线程函数ftn的形参都要为void *类型,这是为了方便可以传递任何数据类型;
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>

#include <pthread.h>

struct my_threadfunc_error
{
    int a;
    int b;
};

void* StartFunction(void* arg)
{
    my_threadfunc_error var{1,2};//系统栈空间
    pthread_exit((void*)&var);
}

int main(int argc, char* argv[])
{
    pthread_t my_pthread_t = 0;
    if (0 != pthread_create(&my_pthread_t, NULL, StartFunction, NULL))
    {
        printf("pthread_create error!\n");
        return -1;
    }
    void* p = NULL;
    if (0 != pthread_join(my_pthread_t, &p))
    {
        printf("pthread_join error!\n");
        return -1;
    }
    my_threadfunc_error* var = (my_threadfunc_error*)(p);
    printf("var.a = %d, var.b = %d\n", var->a, var->b);
    return 0;
}

在这里插入图片描述
上面代码在线程函数中返回的是一个栈上的变量的地址,所在在代码运行到my_threadfunc_error* var = (my_threadfunc_error*)(*p);/的时候,因为线程函数结束,线程的栈也会被回收,p对象已经被释放,自然var结构体变量中的两个数据成员没有初始化,系统随机产生随机数。

所以:
指针所指向的地址必须位于全局区或者malloc分配的空间,不能位于用户栈空间;

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

struct my_threadfunc_error
{
    int a;
    int b;
};

void* StartFunction(void* arg)
{
    my_threadfunc_error* var = (my_threadfunc_error*)malloc(sizeof(my_threadfunc_error));
    var->a = 100;
    var->b = 200;
    pthread_exit((void*)var);
}

int main(int argc, char* argv[])
{
    pthread_t my_pthread_t = 0;
    if (0 != pthread_create(&my_pthread_t, NULL, StartFunction, NULL))
    {
        printf("pthread_create error!\n");
        return -1;
    }
    void* p = NULL;
    if (0 != pthread_join(my_pthread_t, &p))
    {
        printf("pthread_join error!\n");
        return -1;
    }
    my_threadfunc_error* var = (my_threadfunc_error*)(p);
    printf("var.a = %d, var.b = %d\n", var->a, var->b);
    free(var);
    var = NULL;
    return 0;
}

在这里插入图片描述

pthread_detach()

1、背景

【转】在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

pthread_detach()即主线程与子线程分离,资源在线程函数退出时或pthread_exit时自动会被释放

2、函数原型

int pthread_detach(pthread_t tid);

3、返回值

  • 若成功则返回0,若出错则为非零。
  • 如果检测到以下任一情况,pthread_detach()将失败并返回相应的值。
    • EINVAL:tid是分离线程
    • ESRCH:tid不是当前进程中有效的为分离线程

4、detached状态

(1)在线程创建时将其属性设为分离状态(detached);
unjoinable属性可以在pthread_create时指定

	pthread_t pid;
	int rc;
	pthread_attr_t attr;
	pthread_t thread;
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&pid, &attr, PrintHello, NULL);
	pthread_attr_destroy (&attr);

(2)在线程创建后将其属性设为分离的(detached)
在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。

5、pthread_join和pthread_detach小实验对比

1. pthread_detach小实验

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
 
void pthread1(void);
pthread_t thread;

 
void create_pthread(void)
{
	printf("create pthread1:\n");
	pthread_create(&thread,NULL,(void *)&pthread1,NULL);
	pthread_detach(thread);

}
 

void pthread1(void)
{
	while(1)
	{
	printf("-----starting  pthread1-------\n");
	sleep(1);
	}	
	return;
}
 
int main(void)
{	
	printf("-----starting main pthread-------\n");
	create_pthread();
	//sleep(5);
 	//pthread_join ( thread, NULL );
	printf("end main pthread\n");	
	return 0;
}

在这里插入图片描述
可以发现当主进程运行到

printf("end main pthread\n");	

以后,由于线程是分离状态,并且sleep(1);

pthread_detach(thread);

void pthread1(void)
{
	while(1)
	{
	printf("-----starting  pthread1-------\n");
	sleep(1);
	}	
	return;
}

仍会在整个程序结束前,输出子线程的运行结果。如果子线程中:
sleep(1);注释掉的话,相当于子线程还没开始运行,主线程就结束了。
在这里插入图片描述
2. pthread_join小实验

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
 
void pthread1(void);
pthread_t thread;

 
void create_pthread(void)
{
	printf("create pthread1:\n");
	pthread_create(&thread,NULL,(void *)&pthread1,NULL);
	//pthread_detach(thread);

}
 

void pthread1(void)
{
	while(1)
	{
	printf("-----starting  pthread1-------\n");
	sleep(1);
	}	
	return;
}
 
int main(void)
{	
	printf("-----starting main pthread-------\n");
	create_pthread();
	//sleep(5);
 	pthread_join ( thread, NULL );
	printf("end main pthread\n");	
	return 0;
}

在这里插入图片描述
由于线程执行的函数为

void pthread1(void)
{
	while(1)
	{
	printf("-----starting  pthread1-------\n");
	sleep(1);
	}	
	return;
}

并且,主线程和线程是不分离的,则一直循环下去。

pthread_concel()

1. 定义

  • 一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。
  • 线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
  • 线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

2、函数原型

int pthread_cancel(pthread_t thread)

发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

void pthread_testcancel(void)

检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

int pthread_setcancelstate(int state, int *oldstate)

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。

3、应用举例

//https://www.cnblogs.com/xiedan/archive/2009/12/16/1625977.html
#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

pthread_t pid[3];

void* thread_run_1(void* arg){
    cout<<"Now in the thread 1"<<endl;

    int sum = 0;

    int state, oldstate;

    state = PTHREAD_CANCEL_DEFERRED;
    pthread_setcancelstate(state, &oldstate);

    cout<<"oldstate is "<<(state == oldstate? "Deferred":"Async")<<endl;

    //耗时间的循环
    for (int i = 1; i<=INT_MAX; ++i);
  
    cout<<"before testcancel"<<endl;
    pthread_testcancel();

    cout<<"after testcancel"<<endl;

    cout<<"thread 1 done!"<<endl;

}

void* thread_run_2(void* arg){
    cout<<"Now in the thread 2"<<endl;
    pthread_cancel(pid[1]);
    sleep(2);
    cout<<"thread 2 done!"<<endl;
}

int main(){
    pid[0] = pthread_self();
    if (pthread_create(&pid[1],NULL,thread_run_1,NULL) != 0){
        cout<<"error create thread 1"<<endl;
        return -1;
    }
    if (pthread_create(&pid[2],NULL,thread_run_2,NULL) != 0){
        cout<<"error create thread 2"<<endl;
        return -1;
    }

    sleep(5);

    cout<<"Main thread done!"<<endl;
}

在这里插入图片描述

pthread_kill()

1.定义

  • 向同一进程下的另一线程发送信号,信号为0时用于检查此线程ID的线程是否存活。
  • 向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。

2、函数原型

 int pthread_kill(pthread_t threadId,int signal); 

返回值:

  • 成功:0 线程不存在:ESRCH 信号不合法:EINVAL
int kill_ret = pthread_kill(thread_id,0);
if(kill_ret == ESRCH)
printf("指定的线程不存在或者是已经终止\n");
else if(kill_ret == EINVAL)
printf("调用传递一个无用的信号\n");
else
printf("线程存在\n");

3、应用举例

//https://blog.csdn.net/qu1993/article/details/105580924
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
 
void *func(void *argv)
{
    while (1) {
        printf("I am thread, %ld\n", pthread_self());
        sleep(1);
    }
    return NULL;
}
 
int main()
{
    pthread_t t;
    pthread_create(&t, NULL, func, NULL);
    sleep(5);
    printf("send kill to thread\n");
    pthread_kill(t, SIGKILL);
    sleep(10);//线程却没有实现signal处理函数,则整个进程退出。
    printf("end\n");
}

在这里插入图片描述

参考

1、https://www.cnblogs.com/wainiwann/p/3550748.html
2、https://blog.csdn.net/computerme/article/details/52421928
3、https://www.cnblogs.com/zhangfeionline/p/5975272.html
4、https://blog.csdn.net/qq_33883085/article/details/89425923
5、https://blog.csdn.net/zeqi1991/article/details/96642285
6、https://www.cnblogs.com/mcy0808/p/7466614.html
7、https://www.cnblogs.com/xiedan/archive/2009/12/16/1625977.html
8、https://blog.csdn.net/qu1993/article/details/105580924

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值