Linux-C 线程

Linux-C 线程

一、简述

   记--线程的相关练习,线程锁:互斥锁、读写锁、条件变量。一个进程可以有多个线程,线程共享进程的资源(也就是说一个进程的多个线程操作的是同一片内存空间,但是线程具有独立的线程栈),可以使用man手册查看相关函数的使用。

二、线程的基本操作

线程的创建、退出(本身执行退出操作)、取消(由其他线程执行,如在主线程a取消线程b)

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:线程的属性设置,可以通过pthread_attr_init来去初始化这个结构体:pthread_attr_t
            如果设置为NULL则代表按照线程默认属性来设置。

start_routine:线程启动之后执行的第一个函数 的函数指针。

arg:传输给start_routine函数的参数。

返回值

成功:返回0

失败:返回错误变量值,你可以通过strerror这个函数将这个错误值转化为错误信息输出

           EINVAL  设置的属性无效

备注

引用线程,在编译的时候需要链接线程库,编译时添加:-pthread

线程的属性设置中该注意的地方:
    在线程的创建函数pthread_create中,第二参数代表线程的属性,其中它具备以下特点:
        1,如果填入NULL,则按照线程的默认属性来创建线程
        2,如果想要设置线程的属性,需要先初始化pthread_attr_t的属性,用到以下函数
    
线程属性初始化
    #include <pthread.h>

       int pthread_attr_init(pthread_attr_t *attr);//初始化属性

       int pthread_attr_destroy(pthread_attr_t *attr);//销毁属性


线程属性获取函数:
    pthread_getattr_np获取线程属性pthread_attr_t
    #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <pthread.h>

       int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);
    thread:需要获取的线程属性的TID
    attr:属性将会存放到这里


    pthread_self:获取线程自己的TID
        pthread_t pthread_self(void);


线程的分离属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        设置线程的分离属性:
        detachstate有两种状态:
                PTHREAD_CREATE_DETACHED:分离(线程创建之后,不需要接合,结束后直接释放其资源)
                PTHREAD_CREATE_JOINABLE:接合

       int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
        获取线程分离属性


线程的栈属性
    posix_memalign:申请指定的栈内存地址
        #include <stdlib.h>

               int posix_memalign(void **memptr, size_t alignment, size_t size);
            memptr:栈的内存起始地址将会放到这里
            alignment:采用多少个字节对齐内存
            size:申请多大的内存,以字节为单位

    设置栈的大小及内存开始地址:
        int pthread_attr_setstack(pthread_attr_t *attr,
                                 void *stackaddr, size_t stacksize);
        attr:线程的属性变量
        stackaddr:栈的起始地址
        stacksize:栈的大小

               int pthread_attr_getstack(const pthread_attr_t *attr,
                                 void **stackaddr, size_t *stacksize);
        获取栈信息,同上


    直接设置栈内存大小,而不指定内存地址
        int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
            attr:线程的属性变量
            stacksize:栈的大小

               int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
            获取栈的大小信息,参数同上


线程的优先级调度属性
    非实时系统上层软件设置不了静态优先级

静态优先级0-99:
    0:非实时普通线程
    1-99:实时线程(数字越高优先级越高)


动态优先级:
    -20-》19
    数字越大,优先级越低


调度策略:
    OTHER:非实时线程的调度策略
    RR:轮询调度,每一个线程都会分配时间片
    FIFO:抢占式调度
 

pthraed_exit()函数
功能 退出当前线程
头文件 #include <pthread.h>
原型 void pthread_exit(void *retval);
参数 retval:传输给接合它的线程的数据的 存放地址
返回值 无返回值
备注 线程内部自己执行退出
pthread_join()函数
功能 接合一个终止的线程(等待某个线程的终止)
头文件 #include <pthread.h>
原型 int pthread_join(pthread_t thread, void **retval);
参数

thread:要接合的线程的id

retval:用来接收所接合线程的返回值。

返回值

成功:返回0

失败:返回错误号

备注 可以使用strerror()函数将错误号对应的错误信息以字符串的形式输出

测试代码1:

#include <stdio.h>
#include <pthread.h>
#include <string.h>//strerror()

void* thread(void *arg)
{
	printf("arg=%s\n", (char*)arg);
	return "world";
}


int main(void)
{
	pthread_t tid;
	int retval;
	void *retp;

	retval = pthread_create( &tid, NULL, thread, "hello");//创建线程
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}


	retval = pthread_join(tid, &retp);//wait
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	printf("join success, retp=%s\n", (char *)retp);

	return 0;
}

运行结果:

 

测试代码2:

#include <stdio.h>
#include <pthread.h>
#include <string.h>//strerror()

void test(char *arg)
{
	printf("arg=%s\n", arg);
}

void* thread(void *arg)
{
	char *str_arg = (char*)arg;
	if(strcmp(str_arg, "hello") == 0)
	{
		test(str_arg);
		pthread_exit("world--exit");
	}
	
	printf("=======");
	return "world--return";
}


int main(void)
{
	pthread_t tid;
	int retval;
	void *retp ;

	retval = pthread_create( &tid, NULL, thread, "hello");//创建线程
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}


	retval = pthread_join(tid, &retp);//wait
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	printf("join success, retp=%s\n", (char *)retp);

	return 0;
}

运行结果:pthread_exit()

测试代码3:带多个参数

#include <stdio.h>
#include <pthread.h>
#include <string.h>//strerror()
#include <stdlib.h>//calloc()

typedef struct s_student
{
	int s_id;
	char s_name[16];
}Student;

typedef struct s_ret_info
{
	int ret_num;
	char info[64];
}Retinfo;

void* thread(void *arg)
{
	Student *stu = (Student*) arg;
	printf("arg==> s_id=%d, s_name=%s\n", stu->s_id, stu->s_name);

	Retinfo *retp = (Retinfo*)calloc(1, sizeof(Retinfo));
	retp->ret_num = 100;
	strcpy( retp->info, "is ok" );

	return (void*)retp;
}


int main(void)
{
	pthread_t tid;
	int retval;
	void *retp;

	Student stu1 = { 18, "liang"};

	retval = pthread_create( &tid, NULL, thread, &stu1);//创建线程
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}


	retval = pthread_join(tid, &retp);//wait
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	Retinfo *retinfo = (Retinfo*) retp;
	printf("join success, retinfo==>:ret_num=%d, info=%s\n", retinfo->ret_num, retinfo->info);

	return 0;
}

运行结果:

三、互斥锁

如果在man手册中没有相关函数的信息则需要更新man手册。

           更新posix手册man,使用apt在线更新命令:apt-get-install manpages-posix-dev

                 

                 测试网络是否连通

                 

       创建线程一般先创建先运行,但是也有可能由于CPU等资源的原因,某个线程被阻塞而没能够先运行。就算是先运行了但是也有可能由于资源不足等原因而执行"缓慢",导致某一些操作"异常"(有一些条件已经可能被其它的线程所更改,但还是以之前的条件执行操作)。因此,对于某一些执行动作的条件或者是资源进行暂时的“加锁”操作,等操作完成在进行"解锁"操作。

应用场景:多线程访问共有资源的时候需要加锁。

                   例如:假设a线程,b线程都需要对全局变量num进行操作,num的初值为300,a线程需要-50,b线程需要+100,

                             正常来说:a、b线程都操作完之后num的值应该变为:300-50+100=350;即:a读取num的值(300),然后将num-50==>250,再将num的结果值(250)写回去,然后b程序读取num的值(250),然后将num+100==>350,将num的值(350)写回去。(也可以是b线程先操作)

                              

若是在a线程做完-50操作,还没来得及见num写回去,b线程就开始读取了,那么a线程的操作结果可能会被覆盖而失效。

           

 测试代码:演示没有加锁的情况,线程内部对同一个全局变量进行操作,不同线程的执行情况(资源条件也有可能是不同的)是不同的,所消耗的时间也可能不一样。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()
#include <stdlib.h>//rand() 获取随机数
#include <string.h>//strerror()

int num = 300;
int start = 1;//开始对num执行操作的标志

void* thread_a(void *arg)
{
	int tmp;
	while(start);//使得a线程、b线程同时开始操作num, 因为实在主线程创建的,先行创建的很有可能先执行

	tmp = num ;printf("a: read num\n");
	sleep(rand()%5);//do somethings

	tmp -= 50;printf("a: sub num 50\n");
	num = tmp;printf("a: update num \n");
	
	return (void*)0;
}

void* thread_b(void *arg)
{
	int tmp;
	while(start);
	
	tmp = num ;printf("b: read num\n");
	sleep(rand()%5);//do somethings

	tmp += 100;printf("b: add num 100\n");
	num = tmp;printf("b: update num  \n");
	
	return (void*)0;
}

int main(void)
{
	printf("before: num=%d\n", num);
	
	pthread_t tid_a, tid_b;
	int retval;

	retval = pthread_create( &tid_a, NULL, thread_a, NULL);//创建线程a
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}

	retval = pthread_create( &tid_b, NULL, thread_b, NULL);//创建线程b
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}

	sleep(5);//确保a线程、b线程都已经创建并运行起来
	start = 0;//开始让a线程、b线程同时开始 对num进行操作

	retval = pthread_join(tid_a,NULL );//等待线程a
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	retval = pthread_join(tid_b,NULL);//等待线程b
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	printf("after: num=%d\n", num);

	return 0;
}

运行结果:

===============================

互斥锁:对资源进行加锁,防止在操作的时候,某一些条件被其他线程改变。在a已经加锁的情况下b想要加锁,b会被阻塞,等待;直到a把锁解开,b才可以加锁。

pthread_mutex_init()函数
功能 初始化互斥锁
头文件 #include <pthread.h>
原型 int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数

mutex:锁变量,一般定义为全局变量。其中restrict 限定符只适用于指针,表明该指针是访问一个               数据对象的唯一且初始的方式。

attr:互斥锁的属性,设置为NULL,则按照默认设置去配置锁的属性。

返回值

成功:返回0

失败:返回错误号

备注  
pthread_mutex_lock()函数
功能 锁定互斥锁
头文件 #include <pthread.h>
原型 int pthread_mutex_lock(pthread_mutex_t *mutex);
参数 mutex:锁变量
返回值

成功:返回0

失败:返回错误号

备注 当“资源”已经被其它线程加锁了,此时加锁会被阻塞,等待直到资源被其它线程解锁之后才可以加锁。而pthread_mutex_trylock()函数则是尝试性地加锁,如果已经被其它线程加锁了则不会等待,会立即返回。
pthread_mutex_unlock()函数
功能 解开互斥锁
头文件 #include <pthread.h>
原型 int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数 mutex:锁变量
返回值

成功:返回0

失败:返回错误号

备注  
pthread_mutex_destroy函数
功能 销毁互斥锁
头文件 #include <pthread.h>
原型 int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数 mutex:锁变量
返回值

成功:返回0

失败:返回错误号

备注  

 测试代码2:加锁,确保操作的执行顺序(或者说操作的原子性),即同一时间只有一方能够操作,操作完才轮到下一个

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()
#include <stdlib.h>//rand()
#include <string.h>//strerror()

pthread_mutex_t g_mutex;//锁变量
int num = 300;
int start = 1;

void* thread_a(void *arg)
{
	int tmp;
	while(start);
	
	pthread_mutex_lock(&g_mutex);////操作前加锁
	tmp = num ;printf("a: read num\n");
	sleep(rand()%5);//do somethings

	tmp -= 50;printf("a: sub num 50\n");
	num = tmp;printf("a: update num \n");
	pthread_mutex_unlock(&g_mutex);//操作后解锁
	
	return (void*)0;
}

void* thread_b(void *arg)
{
	int tmp;
	while(start);
	
	pthread_mutex_lock(&g_mutex);////操作前加锁
	tmp = num ;printf("b: read num\n");
	sleep(rand()%5);//do somethings

	tmp += 100;printf("b: add num 100\n");
	num = tmp;printf("b: update num  \n");
	pthread_mutex_unlock(&g_mutex);//操作后解锁
	
	return (void*)0;
}

int main(void)
{
	printf("before: num=%d\n", num);
	
	pthread_mutex_init(&g_mutex, NULL);//初始化锁
	pthread_t tid_a, tid_b;
	int retval;

	retval = pthread_create( &tid_a, NULL, thread_a, NULL);//创建线程a
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}

	retval = pthread_create( &tid_b, NULL, thread_b, NULL);//创建线程b
	if(retval)
	{
		fprintf(stderr, "create thread error = %s\n", strerror(retval));
		return -1;
	}

	sleep(5);
	start = 0;

	retval = pthread_join(tid_a,NULL );//等待线程a
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	retval = pthread_join(tid_b,NULL);//等待线程b
	if(retval)
	{
		fprintf(stderr, "join thread error = %s\n", strerror(retval));
		return -1;
	}

	printf("after: num=%d\n", num);
	pthread_mutex_destroy(&g_mutex);//销毁互斥锁
	
	return 0;
}

运行结果:

四、读写锁

        读写锁:可以分为读锁、写锁。有时候a线程加锁只是防止某一些条件被其他线程改变,本身a也没有想修改条件,只想读取。其实这种情况下,a、b多个线程可以同时读取;因此,可以加读锁,防止在读取并用来操作的过程 条件值被修改,但是大家都可以读取(读取的时候不会被阻塞)。要是加上写锁,就会互斥,阻塞,就像互斥锁一样。
          也就是说,a加了读锁,同时b也可以加读锁;但是a加了写锁,b就不能加锁(包括读、写锁),等待a解锁之后才可以加锁。
 

pthread_rwlock_init()函数
功能 初始化读写锁
头文件 #include <pthread.h>
原型  int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
参数

rwlock:锁变量

attr:读写锁的属性,设置为NULL,则按照默认设置去配置锁的属性。

返回值

成功:返回0

失败:返回错误号

          EINVAL 属性值无效

备注  
pthread_rwlock_rdlock()函数
功能 加读锁
头文件 #include <pthread.h>
原型 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
参数 rwlock:锁变量
返回值

成功:返回0

失败:返回错误号

备注

如果“资源”已经被其它线程加了读锁,可以同时加读锁,不会阻塞。

如果被其它线程加了写锁,那么会被阻塞。pthread_rwlock_tryrdlock()函数尝试加锁,无论是否成功都立即返回。

pthread_rwlock_init()函数
功能 加写锁
头文件 #include <pthread.h>
原型 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
参数 rwlock:锁变量
返回值

成功:返回0

失败:返回错误号

备注 当“资源”已经被其它线程加锁了,此时加锁会被阻塞,等待直到资源被其它线程解锁之后才可以加锁。而pthread_rwlock_trywrlock()函数则是尝试性地加锁,如果已经被其它线程加锁了则不会等待,会立即返回。
pthread_rwlock_unlock()函数
功能 解开读写锁
头文件 #include <pthread.h>
原型 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数 rwlock:锁变量
返回值

成功:返回0

失败:返回错误号

备注  
pthread_rwlock_destroy()函数
功能 销毁读写锁
头文件 #include <pthread.h>
原型 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数 rwlock:锁变量
返回值

成功:返回0

失败:返回错误号

备注  

测试代码:

#include <stdio.h>
#include <unistd.h>//sleep()
#include <pthread.h>

pthread_rwlock_t g_rwlock;//读写锁
int start = 1;

void *read_thread(void *arg)//读线程
{
	int count = 3;
	while(start);

	pthread_rwlock_rdlock(&g_rwlock);//加上读锁

	while(count--)
	{
		printf("read: thread id=%lu\n", pthread_self());//pthread_self()获取自身的线程ID
		sleep(1);
	}
	
	pthread_rwlock_unlock(&g_rwlock);//解开读锁
	return NULL;
}

void *write_thread(void *arg)//写线程
{
	int count = 3;
	while(start);
	
	pthread_rwlock_wrlock(&g_rwlock);//加上写锁

	while(count--)
	{
		printf("write: thread id=%lu\n", pthread_self());
		sleep(1);
	}

	pthread_rwlock_unlock(&g_rwlock);//解开写锁

	return NULL;
}

int main(void)
{
	pthread_t tid_r, tid_w;

	pthread_rwlock_init(&g_rwlock, NULL);//初始化读写锁

	//创建读,写线程
	pthread_create(&tid_w, NULL, write_thread, NULL);
	pthread_create(&tid_r, NULL, read_thread, NULL);

	//确保所有线程都已经创建完毕并开始执行
	sleep(5);
	start = 0;

	//等待读、写线程退出
	pthread_join(tid_w, NULL);
	pthread_join(tid_r, NULL);
	
	pthread_rwlock_destroy(&g_rwlock);//销毁读写锁

	return 0;
}

运行结果:

读写锁互斥(阻塞)

读读锁同时操作(不阻塞)

写写锁互斥(阻塞)

五、线程的取消机制

        

pthread_cancel()函数
功能 取消某个线程(例如主线程取消子线程)
头文件 #include <pthread.h>
原型 int pthread_cancel(pthread_t thread);
参数 thread:指定线程的ID,当这个线程收到取消请求之后,默认在遇到取消点函数(具体内参照man 7 pthreads)的时候退出线程;
返回值

成功:返回0

失败:返回非0的错误号

备注 通常用于主线程取消子线程,或者是释放资源的时候
pthread_setcancelstate()函数
功能 设置线程的取消机制的状态
头文件 #include <pthread.h>
原型 int pthread_setcancelstate(int state, int *oldstate);
参数 state:
            PTHREAD_CANCEL_ENABLE:默认设置,代表该线程可取消
            PTHREAD_CANCEL_DISABLE:代表该线程不可取消
        oldstate:用来保存旧的取消状态,设置为NULL,则不保存
返回值 成功:返回0
失败:返回非0的错误号
备注

有关安全取消点函数,详情:man 7 pthreds

pthread_setcanceltype()函数
功能 设置线程的响应方式
头文件 #include <pthread.h>
原型 int pthread_setcanceltype(int type, int *oldtype);
参数

type: PTHREAD_CANCEL_DEFERRED 遇到取消点的时候取消(默认设置)
            PTHREAD_CANCEL_ASYNCHRONOUS 立即取消线程

        oldtype:用来保存旧的设置,设置为NULL,则不保存

返回值 成功:返回0
失败:返回非0的错误号
备注 例如正在执行某一些重要的函数,不希望被随意打断的,一般默认不会立即取消,直到执行完成并执行到的是可以取消的"位置"。(关于取消点 具体内参照man 7 pthreads)
pthread_cleanup_push()函数
功能 登记函数(添加在线程收到取消通知的时候,在取消前补充执行的函数,取消前要执行的函数,比如释放资源、保存数据等)
头文件 #include <pthread.h>
原型 void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数 routine:需要在退出前执行的函数 的函数指针
arg:执行函数(routine)的参数
返回值
备注 原则:和栈一样,后入先出;对应入栈操作。
pthread_cleanup_pop()函数
功能 在线程取消的压栈函数后,将登记的函数清除出去
头文件 #include <pthread.h>
原型 void pthread_cleanup_pop(int execute);
参数

execute:0代表清除登记的函数,不执行里面的函数 (出栈的时候不执行函数)
                 非0代表,清除出去,并且执行里面的函数 (出栈的时候执行函数)

                这个参数并不影响异常终止时清理函数的执行。调用 pthread_exit()和取消点终                   止(pthread_cancel())都将执行pthread_cleanup_push()所指定的清理函数。

返回值
备注 对应出栈操作。

测试代码:取消线程,pthread_cancel()

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void* thread( void* arg)
{
	int i;
	
	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		sleep(1);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	getchar();
	pthread_cancel(tid);
	
	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码2:设置线程不可以取消

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void* thread( void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//设置线程不可以取消
	int i;
	
	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		sleep(1);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	getchar();
	pthread_cancel(tid);
	
	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码3:pthread_cleanup_push()与pthread_cleanup_pop()

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		sleep(1);
	}

	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	getchar();
	pthread_cancel(tid);
	
	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码4:对于pthread_exit()  退出线程

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		if(i == 5)
		{
			pthread_exit(NULL);
		}
		sleep(1);
	}

	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码5:return退出线程时,不会执行所登记 的函数

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		if(i==3)
		{
			return NULL;
		}
		sleep(1);
	}

	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码6:设置为清除(出栈)但是不执行。如果是由pthread_cancel()引起的,所登记的函数都将会执行,即使pthread_cleanup_pop(0)。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		sleep(1);
	}

	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	getchar();
	pthread_cancel(tid);
	
	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码7:设置为清除(出栈)但是不执行。如果是由pthread_exit()引起的,所登记的函数都将会执行,即使pthread_cleanup_pop(0)。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<10; i++)
	{
		printf("i=%d\n", i);
		if(i==3)
		{
			pthread_exit(NULL);
		}
		sleep(1);
	}

	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

测试代码8:设置为清除(出栈)但是不执行。(ctrl+c中断也不会执行)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

void clearup(void *arg)
{
	printf("arg=%s\n", (char*) arg);
}

void* thread( void* arg)
{
	int i;
	pthread_cleanup_push(clearup, "1,保存数据!");
	pthread_cleanup_push(clearup, "2,释放资源!");

	for(i=0; i<5; i++)
	{
		printf("i=%d\n", i);
		sleep(1);
	}

	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;

	pthread_create(&tid, NULL, thread, NULL);

	pthread_join(tid, NULL);
	printf("cancel %lu\n", tid);
	return 0;
}

运行结果:

设置为清除(出栈)并执行

六、条件变量(一般配合互斥锁使用)

        应用场景:某线程想要操作某个全局资源、在操作时需要加锁,但是加锁之后发现资源尚未满足进一步的操作条件,此时需要等待资源变化并满足条件,但是由于已经加锁,别的线程无法改变资源条件,所以需要解锁再等待。直到资源满足并被唤醒,对资源进行加锁并进行相应的操作。使用条件变量来帮我们做:加锁、等待、唤醒、加锁。

pthread_cond_init()函数
功能 初始化条件变量
头文件 #include <pthread.h>
原型 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数

cond:条件变量

attr:条件变量的属性,设置为NULL,则使用默认设置。

返回值

成功:返回0

失败:返回错误号

备注  
pthread_cond_wait()函数
功能 等待条件条件
头文件 #include <pthread.h>
原型 int pthread_cond_wait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex);
参数

cond:条件变量 (需要进入睡眠的条件变量)

mutex:锁变量(需要操作的互斥锁)

返回值

成功:返回0

失败:返回错误号

备注 若是条件不满足则让线程进入睡眠并解锁muext互斥锁,一直等到(条件满足)cond被唤醒,,唤醒之后再给mutex上锁。注:其中的解锁操作就是为了让“别人”改变资源条件。
pthread_cond_signal()函数
功能 发出信号(唤醒单个条件变量等待线程)
头文件 #include <pthread.h>
原型 int pthread_cond_signal(pthread_cond_t *cond);
参数 cond:条件变量
返回值

成功:返回0

失败:返回错误号

备注  
pthread_cond_broadcast()函数
功能 唤醒所有(因某个条件变量而)睡眠的线程
头文件 #include <pthread.h>
原型 int pthread_cond_broadcast(pthread_cond_t *cond);
参数 cond:条件变量
返回值

成功:返回0

失败:返回错误号

备注 先睡眠的线程先被唤醒(类似排队)。
pthread_cond_destroy()函数
功能 销毁条件变量
头文件 #include <pthread.h>
原型 int pthread_cond_destroy(pthread_cond_t *cond);
参数 cond:条件变量
返回值

成功:返回0

失败:返回错误号

备注  

测试代码:资源条件count开始还没有满足,子线程进入睡眠,等待资源满足。主线程改变资源使得条件满足。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//sleep()

pthread_mutex_t g_mutex;//互斥锁变量
pthread_cond_t g_cond;//条件变量
int count = 0;//资源条件

void *child_thread(void *arg)
{

	pthread_mutex_lock(&g_mutex);//加互斥锁

	printf("thread id = %lu ==〉start\n", pthread_self());
	
	while(1)
	{
		printf("in while 1\n");

		if(count >= 100)//资源条件满足
		{
			sleep(1);
			count -= 100;
			break;
		}
		else //资源条件不满足
		{
			//等待条件满足,(解开互斥锁,使得线程睡眠,等收到条件满足的信号,再加上互斥锁)
			pthread_cond_wait(&g_cond, &g_mutex);
		}

		printf("in while 2\n");
		
	}

	pthread_mutex_unlock(&g_mutex);//解开互斥锁

	printf("thread id = %lu ==>exit\n", pthread_self());

	return NULL;
}


int main(void)
{
	pthread_t tid;

	pthread_mutex_init(&g_mutex, NULL);//初始化互斥锁
	pthread_cond_init(&g_cond, NULL);//初始化条件变量

	pthread_create(&tid, NULL, child_thread, NULL);//创建子线程

	sleep(1);//确保子线程创建并运行

	//加上互斥锁,此处因为子线程已经加锁而被阻塞,
	//等到子线程因为资源条件不足而进入睡眠并解开互斥锁,才能加锁
	pthread_mutex_lock(&g_mutex);

	printf("main start input\n");
	scanf("%d", &count);//用来改变资源条件

	pthread_mutex_unlock(&g_mutex);//解开互斥锁
	
	if(count>=100)//如果资源条件满足
	{
		pthread_cond_signal(&g_cond);//发送信号,关心此条件变量的线程就会被唤醒
	}
	
	pthread_join(tid, NULL);//等待子进程退出
	
	pthread_mutex_destroy(&g_mutex);//销毁互斥锁
	pthread_cond_destroy(&g_cond);//销毁条件变量
	
	printf("cunt=%d\n", count);
	return 0;
}

运行结果:

七、线程池

         通常在多任务时,用来管理线程。

         应用场景:在多任务时,需要多线程来提高效率。有时候任务比较多,有时候任务又比较少。在任务多的时候,需要创建多个线程需要时间、不需要的时候又要释放资源,如果是不断的创建线程、然后不断的释放线程会比较浪费CPU资源,并且相应时间也较慢。为此如果提前创建好线程的话,就可以省去创建线程的时间,以提高执行效率。提前创建好的合适数量的线程、有任务的时候直接响应、如果没有任务则进入休眠、任务多的时候再添加线程。总之维持最少线程数、以及最大线程数来确保执行了效率。可以想象有一个装有已经创建好的线程的 池塘,有任务时我们可以直接拿来用、没有任务时、线程进入休眠。线程池的线程数量维持在一定数量,根据任务的多少进行调整,但是会保持一定最少数量和虽大数量。或者说线程池就是一个说法,用来形容我们管理线程与任务的方式。

测试代码:    网盘连接:链接: https://pan.baidu.com/s/1rda9akGqPZwvv7vMeprwBQ 密码: ersk

main.c文件

#include "thread_pool.h"

void *mytask(void *arg)//任务
{
	type_t n = (type_t)arg;

	printf("[%u][%s] ==> job will be done in %lu sec...\n",
		(unsigned)pthread_self(), __FUNCTION__, n);//__FUNCTION__:宏-〉函数名

	sleep(n);//do somethings

	printf("[%u][%s] ==> job done!\n",
		(unsigned)pthread_self(), __FUNCTION__);

	return NULL;
}

void *count_time(void *arg)//计时函数
{
	int i = 0;
	while(1)
	{
		sleep(1);
		printf("sec: %d\n", ++i);
	}
}

int main(void)
{
	pthread_t a;
	pthread_create(&a, NULL, count_time, NULL);//创建子线程用来计时

	// 1, 初始化线程池
	thread_pool *pool = malloc(sizeof(thread_pool));
	init_pool(pool, 2);//创建2条线程

	// 2, 添加3个任务
	printf("throwing 3 tasks...\n");
	add_task(pool, mytask, (void *)((type_t)rand()%10));
	add_task(pool, mytask, (void *)((type_t)rand()%10));
	add_task(pool, mytask, (void *)((type_t)rand()%10));

	// 3, 查看当前的线程数目
	printf("current thread number: %d\n",remove_thread(pool, 0));
	
	sleep(9);//延时等待

	// 4, 添加2个任务
	printf("throwing another 2 tasks...\n");
	add_task(pool, mytask, (void *)((type_t)rand()%10));
	add_task(pool, mytask, (void *)((type_t)rand()%10));

	// 5, 添加线程
	add_thread(pool, 2);
	printf("add 2 thread \n");
	sleep(5);

	// 6, 移除线程
	printf("remove 3 threads from the pool, "
	       "current thread number: %d\n",
			remove_thread(pool, 3));

	// 7, 销毁线程池
	destroy_pool(pool);
	
	//释放内存空间
	free(pool);
	
	return 0;
}

thread_pool.h文件

#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include <errno.h>
#include <pthread.h>

#define MAX_WAITING_TASKS	1000	//最大的等待任务数量
#define MAX_ACTIVE_THREADS	20		//最大的线程数量
typedef unsigned long type_t;

struct task					//任务链表结构体
{
	void *(*task)(void *arg);	//任务做什么事情的函数指针
	void *arg;					//传入函数的参数

	struct task *next;			//链表的位置结构体指针
};

typedef struct thread_pool
{
	pthread_mutex_t lock;		//用于线程池同步互斥的互斥锁
	pthread_cond_t  cond;		//用于让线程池里面的线程睡眠的条件变量
	struct task *task_list;		//线程池的执行任务链表

	pthread_t *tids;			//线程池里面线程的ID登记处

	unsigned waiting_tasks;		//等待的任务数量,也就是上面任务链表的长度
	unsigned active_threads;	//当前已经创建的线程数

	bool shutdown;				//线程池的开关
}thread_pool;


bool
init_pool(thread_pool *pool,
          unsigned int threads_number);		//初始化线程池

bool
add_task(thread_pool *pool,
         void *(*task)(void *arg),
         void *arg);						//往线程池里面添加任务节点

int 
add_thread(thread_pool *pool,
           unsigned int additional_threads_number);	//添加线程池中线程的数量

int 
remove_thread(thread_pool *pool,
              unsigned int removing_threads_number);	//移除线程

bool destroy_pool(thread_pool *pool);			//销毁线程池
void *routine(void *arg);						//线程池里面线程的执行函数

#endif

thread_pool.c文件

#include "thread_pool.h"

void handler(void *arg)				
{
	pthread_mutex_unlock((pthread_mutex_t *)arg);	//防止死锁,所以在这里添加解锁操作
}
/* 每一个线程池中的线程所执行的内容, arg就是线程池的地址 */
void *routine(void *arg)
{
	thread_pool *pool = (thread_pool *)arg;		//将线程池的地址存放进去pool	
	struct task *p;								//定义一个缓冲指针,后期任务队列遍历的时候使用

	while(1)
	{

		pthread_cleanup_push(handler, (void *)&pool->lock);//提前登记线程被取消后需要处理的事情		
		pthread_mutex_lock(&pool->lock);				//由于需要操作线程池中的共有资源,所以加锁		


		while(pool->waiting_tasks == 0 && !pool->shutdown)	//判断是否没有需要运行的任务		
		{
			pthread_cond_wait(&pool->cond, &pool->lock);	//让线程睡眠		
		}


		if(pool->waiting_tasks == 0 && pool->shutdown == true)	//判断线程池是否没有任务并且需要关闭	
		{
			pthread_mutex_unlock(&pool->lock);	//解锁
			pthread_exit(NULL);					//退出线程					
		}


		p = pool->task_list->next;			//让p登记需要运行的任务节点						
		pool->task_list->next = p->next;	//将此任务节点从链表中删除							
		pool->waiting_tasks--;				//将等待运行的任务队列-1								

		pthread_mutex_unlock(&pool->lock);	//解锁			
		pthread_cleanup_pop(0);				//解除登记取消线程之后所做的函数								

		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);		//忽略线程的取消操作
		(p->task)(p->arg);						//函数调用									
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);		//重启线程取消操作

		free(p);							//释放任务节点
	}

	pthread_exit(NULL);							
}

/* pool:线程池结构体的地址,threads_number:一开始初始化的线程数量 */
bool init_pool(thread_pool *pool, unsigned int threads_number)			
{
	
	pthread_mutex_init(&pool->lock, NULL);			//初始化互斥锁				
	pthread_cond_init(&pool->cond, NULL);			//初始化条件变量				

	pool->shutdown = false;							//开启线程池的标志false:开	
	pool->task_list = malloc(sizeof(struct task));	//申请任务链表头	
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);//申请最大线程数量的ID内存	

	if(pool->task_list == NULL || pool->tids == NULL)		//判断两个申请的内存是否成功		
	{
		perror("allocate memory error");
		return false;
	}

	pool->task_list->next = NULL;	//初始化链表头,将下一个节点指向NULL

	pool->waiting_tasks = 0;		//将等待运行的任务数量置0	
	pool->active_threads = threads_number;	//登记当前的线程数量		

	int i;
	for(i=0; i<pool->active_threads; i++)				//创建线程池里面的线程			
	{
		if(pthread_create(&((pool->tids)[i]), NULL,
					routine, (void *)pool) != 0)		//每一个线程都去跑routine这个函数的内容
		{
			perror("create threads error");
			return false;
		}
	}

	return true;
}


/* 投放任务:pool:线程池地址;task:任务需要运行的内容的函数指针; arg:传入给task函数的参数 */
bool add_task(thread_pool *pool,
			void *(*task)(void *arg), void *arg)
{
	struct task *new_task = malloc(sizeof(struct task));	//新建一个任务节点
	if(new_task == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	new_task->task = task;							//将任务需要做的函数存进task指针中
	new_task->arg = arg;							//将任务函数参数记录在arg里面
	new_task->next = NULL;							//将任务节点的下一个位置指向NULL


	pthread_mutex_lock(&pool->lock);					//上锁
	if(pool->waiting_tasks >= MAX_WAITING_TASKS)		//判断任务数量有没有超标
	{
		pthread_mutex_unlock(&pool->lock);				//解锁

		fprintf(stderr, "too many tasks.\n");			//反馈太多任务了
		free(new_task);							//释放掉刚才登记的任务节点

		return false;							//返回添加不了任务到任务链表中
	}
	
	struct task *tmp = pool->task_list;					//将线程池中任务链表的头节点登记到tmp
	while(tmp->next != NULL)						//将tmp指向最后的节点的位置
		tmp = tmp->next;

	tmp->next = new_task;							//将新建的任务节点插入到链表中
	pool->waiting_tasks++;							//将等待的任务数量+1


	pthread_mutex_unlock(&pool->lock);					//解锁
	pthread_cond_signal(&pool->cond);					//唤醒正在睡眠中的线程

	return true;								//返回添加成功
}

/* 添加线程到线程池中 pool:线程池结构体地址, additional_threads:添加的线程的数量 */
int add_thread(thread_pool *pool, unsigned additional_threads)
{
	if(additional_threads == 0)
		return 0;

	unsigned total_threads =
		     pool->active_threads + additional_threads;	//将总数记录在这个变量中

	int i, actual_increment = 0;
	for(i = pool->active_threads;
	    i < total_threads && i < MAX_ACTIVE_THREADS;
	    i++)
	{
		if(pthread_create(&((pool->tids)[i]),
				NULL, routine, (void *)pool) != 0)	//新建线程
		{
			perror("add threads error");

			if(actual_increment == 0)
				return -1;

			break;
		}
		actual_increment++; 					//记录成功添加了多少条线程到线程池中
	}

	pool->active_threads += actual_increment;	//将最后成功添加到线程池的线程总数记录线程池中
	return actual_increment;					//返回新建了多少条线程
}

int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	if(removing_threads == 0)
		return pool->active_threads;

	int remain_threads = pool->active_threads - removing_threads;	//将移除线程之后的线程数量登记下来
	remain_threads = remain_threads>0 ? remain_threads:1;			//如果这个数量不大于0,则把它置1

	int i;
	for(i=pool->active_threads-1; i>remain_threads-1; i--)			//从id的最后一位线程开始取消
	{
		errno = pthread_cancel(pool->tids[i]);
		if(errno != 0)
			break;
	}

	if(i == pool->active_threads-1)				//判断是否取消掉要求的数量
		return -1;
	else
		pool->active_threads = i+1;					//将新的线程数量登记active_threads
	
	return pool->active_threads;					//返回剩下多少条线程在线程池中
}

bool destroy_pool(thread_pool *pool)
{

	pool->shutdown = true;					//使能线程池的退出开关
	pthread_cond_broadcast(&pool->cond);	//将所有的线程全部唤醒

	int i;
	for(i=0; i<pool->active_threads; i++)	//开始接合线程
	{
		errno = pthread_join(pool->tids[i], NULL);
		if(errno != 0)
		{
			printf("join tids[%d] error: %s\n",
					i, strerror(errno));
		}
		else
			printf("[%u] is joined\n", (unsigned)pool->tids[i]);
		
	}

	free(pool->task_list);		//释放掉任务头节点
	free(pool->tids);			//释放掉线程ID内存

	return true;
}

Makefile文件

CC = gcc
CFLAGS = -O0 -Wall -g -I include -lpthread

main:src/main.c src/thread_pool.c
	$(CC) $^ -o $@ $(CFLAGS)

debug:src/main.c src/thread_pool.c
	$(CC) $^ -o $@ $(CFLAGS) -DDEBUG

clean:
	$(RM) .*.sw? main debug *.o

.PHONY:all clean

运行结果:

猜你喜欢

转载自blog.csdn.net/nanfeibuyi/article/details/82021017