天天看点

linux多线程编程:创建和取消

线程

  1. 线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  2. 一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
  3. 进程——资源分配的最小单位,线程——程序执行的最小单位"
  4. 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
  5. 线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

一个进程中线程共享的资源:

  1. 可执行的指令(代码)
  2. 静态数据(一般指的是全局变量)
  3. 进程中打开的文件描述符
  4. 当前的工作目录
  5. 用户ID
  6. 用户组ID

每个线程私有的资源包括:

  1. 线程ID(TID)
  2. PC(程序计数器)和相关寄存器 (每个线程就可以执行自己的代码)
  3. 堆栈(栈)
  4. 错误号(errno)
  5. 优先级
  6. 执行状态和属性

多线程编程

多线程需要用到pthread线程库 p是POSIX,即遵循POSIX标准。这里函数执行成功返回0,失败返回错误码(和c库函数有所不同,失败返回-1,成功错误码另行判断)

  • int pthread_create(pthread_t * tidp,const pthread_attr_t*attr,void*(*start_rtn)(void),void*arg)

    第一个参数 tidp:是线程ID,线程创建成功时tidy指向的内存单元被设置为新创建线程的ID(解惑:在使用时直接定义pthread_t型数据传入创建函数中不是传的任意值,而是这个变量用于存放即将新建的线程的ID,传入的是指针,所以传的时候取地址)

    第二个参数 attr:定制不同的线程属性 通常被设置为NULL

    第三个参数 start_rtn:从类型定义上看是函数指针,传入的是这个函数的指针。新创建额线程就在这个函数中运行。

    第四个参数 arg:start_rtn函数的参数,无参数时设置为NULL即可。有参数时输入参数的地址,而多个参数可以用结构体传递。(注意:类型是void型,所以传参时要用强转)

  • void pthread_exit(void *retval)

    参数:retval表示线程退出状态,通常传NULL。

    注意点:

    1:return的作用是返回函数的调用点,在线程函数中和pthread_exit的作用等价。但是main函数中的return则代表主线程的结束即整个进程结束,释放地址空间,所有线程都会终止。而exit和_exit函数也会终止整个进程。

    2、pthread_exit或者return返回的指针(线程执行函数用return或者pthread_exit结束线程时),所指向的内存单元必须是全局的或者是malloc分配的,简而言之,就是变量不能是在函数中定义的,因为结束线程后这个线程函数的栈空间就会被重新分配出去,这样其他线程的返回指针是无意义的。

  • int pthread_join(pthread_t tid,void **rval_ptr)

    参数1 tid:等待退出线程的ID

    参数2 rval_ptr:线程退出的返回值的指针(就是pthread_exit函数返回值的指针)

    在指定线程线程退出前程序会一直阻塞在这里。

  • int pthread_cancel(pthread_t thread)

    用于结束一个指定的线程,相对于使用pthread_exit 函数的主动退出,该函数的使用属于使线程被动结束。

    在使用该函数时的注意点:会出现线程无法被取消的情况是因为在收到取消进程请求后在

    默认情况下

    线程并不会立刻结束,因为它仅仅只是一个请求。而是继续向下走如果遇到取消点才会取消。所以没有被取消就是因为没有设置取消点。

    前面说了是在默认情况下,收到取消请求后,线程将会运行到函数内部的取消点后终止函数。这就要涉及另外相关的函数:

    1、

    int pthread_setcancelstate(int state, int *oldstate)

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

    2、

    int pthread_setcanceltype(int type, int *oldtype)

    设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFERRED和 PTHREAD_CANCEL_ASYNCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和 立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。

    此函数应该在线程开始时执行,若线程内部有任何资源申请等操作,应该选择 PTHREAD_CANCEL_DEFERRED 的设定,然后在退出点(pthread_testcancel 用于定义退出点)进行线程退出。

    3、

    void pthread_testcancel(void)

    检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。 此函数在线程内执行,执行的位置就是线程退出的位置,在执行此函数以前,线程内部的相关资源申请一定要释放掉,他很容易造成内存泄露。

    关于取消点的理解,posix提供一系列自带取消点的函数,而大部分函数的特点是阻塞式的系统调用或者较大花费的系统调用,使得操作系统陷入内核态,而我们知道系统在从内核态切回用户态时会进行一个信号的检查,如果收到信号就会处理信号,而取消信号应该就是在此时被处理,从而取消掉线程。

    (而我在写相关代码测试时会用到printf打印显示代码运行过程发现结果与预期不同,后来发现printf也是属于系统调用函数 ,在printf里面是用的putc函数输出,继续跟踪putc()发现write函数被调用,继续跟踪write函数,声明是在user.h文件中但是没有具体c代码的实现,这就是一个系统调用)

    参考链接: 【Linux多线程】pthread_cancel函数

    参考链接:pthread_cancel为何无法取消掉一个线程

相关代码

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

pthread_t tid[3];

void delay()    //为了与sleep比较,因为sleep算系统调用
{
	int x = 10000, y;
	while (x > 0)
	{
		y = 30000;
		while (y > 0)
		{
			y--;
		}
		x--;
	}
}

void *mythread1(void *arg)
{
	int i = 5;
	while (i--)
	{
		printf("this is mythread1\n");
		//sleep(1);
		delay();
	}

	pthread_exit((void *)10);    //线程退出
}

void *mythread2(void *arg)
{
	int old;
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);  //立即取消  old保存原来的属性
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);  //不允许被取消 old保存原来的属性
	int i = 5;
	while (i--)
	{
		if(i == 3)
		{
			printf("%s,%d\n", (char *)arg,i);
		}
		//usleep(500000);
		delay();
	}
}

struct Test
{
	char name[32];
	int a;
	char b;
};

void *mythread3(void *arg)
{
	struct Test *t = (struct Test *)arg;
	printf("%s %d %c\n", t->name, t->a, t->b);
	pthread_cancel(tid[1]);   //取消线程1  默认属性 线程运行到下一个取消点的时候被取消
}

int main()
{
	int ret;
	ret = pthread_create(&tid[0], NULL, mythread1, NULL);   //线程号 默认属性  线程函数   不需要传参
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	ret = pthread_create(&tid[1], NULL, mythread2, "helloworld");
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	struct Test t = {"aaa", 10, 'x'};
	ret = pthread_create(&tid[2], NULL, mythread3, (void *)&t);
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	void *status;
	pthread_join(tid[0], &status);   //1、等待线程结束  2、回收线程资源
	printf("线程1退出,退出码是%d\n", (int)status);
	pthread_join(tid[1], &status);
	pthread_join(tid[2], &status);

	return 0;
}
           

继续阅读