Linux-C 线程
一、简述
记--线程的相关练习,线程锁:互斥锁、读写锁、条件变量。一个进程可以有多个线程,线程共享进程的资源(也就是说一个进程的多个线程操作的是同一片内存空间,但是线程具有独立的线程栈),可以使用man手册查看相关函数的使用。
二、线程的基本操作
线程的创建、退出(本身执行退出操作)、取消(由其他线程执行,如在主线程a取消线程b)
功能 | 创建线程 |
头文件 | #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 start_routine:线程启动之后执行的第一个函数 的函数指针。 arg:传输给start_routine函数的参数。 |
返回值 | 成功:返回0 失败:返回错误变量值,你可以通过strerror这个函数将这个错误值转化为错误信息输出 EINVAL 设置的属性无效 |
备注 | 引用线程,在编译的时候需要链接线程库,编译时添加:-pthread 线程的属性设置中该注意的地方: int pthread_attr_init(pthread_attr_t *attr);//初始化属性 int pthread_attr_destroy(pthread_attr_t *attr);//销毁属性
int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
int posix_memalign(void **memptr, size_t alignment, size_t size); 设置栈的大小及内存开始地址: int pthread_attr_getstack(const pthread_attr_t *attr,
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
静态优先级0-99:
|
功能 | 退出当前线程 |
头文件 | #include <pthread.h> |
原型 | void pthread_exit(void *retval); |
参数 | retval:传输给接合它的线程的数据的 存放地址 |
返回值 | 无返回值 |
备注 | 线程内部自己执行退出 |
功能 | 接合一个终止的线程(等待某个线程的终止) |
头文件 | #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才可以加锁。
功能 | 初始化互斥锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); |
参数 | mutex:锁变量,一般定义为全局变量。其中restrict 限定符只适用于指针,表明该指针是访问一个 数据对象的唯一且初始的方式。 attr:互斥锁的属性,设置为NULL,则按照默认设置去配置锁的属性。 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 |
功能 | 锁定互斥锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_mutex_lock(pthread_mutex_t *mutex); |
参数 | mutex:锁变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 | 当“资源”已经被其它线程加锁了,此时加锁会被阻塞,等待直到资源被其它线程解锁之后才可以加锁。而pthread_mutex_trylock()函数则是尝试性地加锁,如果已经被其它线程加锁了则不会等待,会立即返回。 |
功能 | 解开互斥锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_mutex_unlock(pthread_mutex_t *mutex); |
参数 | mutex:锁变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 |
功能 | 销毁互斥锁 |
头文件 | #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解锁之后才可以加锁。
功能 | 初始化读写锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr); |
参数 | rwlock:锁变量 attr:读写锁的属性,设置为NULL,则按照默认设置去配置锁的属性。 |
返回值 | 成功:返回0 失败:返回错误号 EINVAL 属性值无效 |
备注 |
功能 | 加读锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); |
参数 | rwlock:锁变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 | 如果“资源”已经被其它线程加了读锁,可以同时加读锁,不会阻塞。 如果被其它线程加了写锁,那么会被阻塞。pthread_rwlock_tryrdlock()函数尝试加锁,无论是否成功都立即返回。 |
功能 | 加写锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); |
参数 | rwlock:锁变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 | 当“资源”已经被其它线程加锁了,此时加锁会被阻塞,等待直到资源被其它线程解锁之后才可以加锁。而pthread_rwlock_trywrlock()函数则是尝试性地加锁,如果已经被其它线程加锁了则不会等待,会立即返回。 |
功能 | 解开读写锁 |
头文件 | #include <pthread.h> |
原型 | int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); |
参数 | rwlock:锁变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 |
功能 | 销毁读写锁 |
头文件 | #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;
}
运行结果:
读写锁互斥(阻塞)
读读锁同时操作(不阻塞)
写写锁互斥(阻塞)
五、线程的取消机制
功能 | 取消某个线程(例如主线程取消子线程) |
头文件 | #include <pthread.h> |
原型 | int pthread_cancel(pthread_t thread); |
参数 | thread:指定线程的ID,当这个线程收到取消请求之后,默认在遇到取消点函数(具体内参照man 7 pthreads)的时候退出线程; |
返回值 | 成功:返回0 失败:返回非0的错误号 |
备注 | 通常用于主线程取消子线程,或者是释放资源的时候 |
功能 | 设置线程的取消机制的状态 |
头文件 | #include <pthread.h> |
原型 | int pthread_setcancelstate(int state, int *oldstate); |
参数 | state: PTHREAD_CANCEL_ENABLE:默认设置,代表该线程可取消 PTHREAD_CANCEL_DISABLE:代表该线程不可取消 oldstate:用来保存旧的取消状态,设置为NULL,则不保存 |
返回值 | 成功:返回0 失败:返回非0的错误号 |
备注 | 有关安全取消点函数,详情:man 7 pthreds |
功能 | 设置线程的响应方式 |
头文件 | #include <pthread.h> |
原型 | int pthread_setcanceltype(int type, int *oldtype); |
参数 | type: PTHREAD_CANCEL_DEFERRED 遇到取消点的时候取消(默认设置) oldtype:用来保存旧的设置,设置为NULL,则不保存 |
返回值 | 成功:返回0 失败:返回非0的错误号 |
备注 | 例如正在执行某一些重要的函数,不希望被随意打断的,一般默认不会立即取消,直到执行完成并执行到的是可以取消的"位置"。(关于取消点 具体内参照man 7 pthreads) |
功能 | 登记函数(添加在线程收到取消通知的时候,在取消前补充执行的函数,取消前要执行的函数,比如释放资源、保存数据等) |
头文件 | #include <pthread.h> |
原型 | void pthread_cleanup_push(void (*routine)(void *),void *arg); |
参数 | routine:需要在退出前执行的函数 的函数指针 arg:执行函数(routine)的参数 |
返回值 | 无 |
备注 | 原则:和栈一样,后入先出;对应入栈操作。 |
功能 | 在线程取消的压栈函数后,将登记的函数清除出去 |
头文件 | #include <pthread.h> |
原型 | void pthread_cleanup_pop(int execute); |
参数 | execute: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;
}
运行结果:
设置为清除(出栈)并执行
六、条件变量(一般配合互斥锁使用)
应用场景:某线程想要操作某个全局资源、在操作时需要加锁,但是加锁之后发现资源尚未满足进一步的操作条件,此时需要等待资源变化并满足条件,但是由于已经加锁,别的线程无法改变资源条件,所以需要解锁再等待。直到资源满足并被唤醒,对资源进行加锁并进行相应的操作。使用条件变量来帮我们做:加锁、等待、唤醒、加锁。
功能 | 初始化条件变量 |
头文件 | #include <pthread.h> |
原型 | int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); |
参数 | cond:条件变量 attr:条件变量的属性,设置为NULL,则使用默认设置。 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 |
功能 | 等待条件条件 |
头文件 | #include <pthread.h> |
原型 | int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); |
参数 | cond:条件变量 (需要进入睡眠的条件变量) mutex:锁变量(需要操作的互斥锁) |
返回值 | 成功:返回0 失败:返回错误号 |
备注 | 若是条件不满足则让线程进入睡眠并解锁muext互斥锁,一直等到(条件满足)cond被唤醒,,唤醒之后再给mutex上锁。注:其中的解锁操作就是为了让“别人”改变资源条件。 |
功能 | 发出信号(唤醒单个条件变量等待线程) |
头文件 | #include <pthread.h> |
原型 | int pthread_cond_signal(pthread_cond_t *cond); |
参数 | cond:条件变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 |
功能 | 唤醒所有(因某个条件变量而)睡眠的线程 |
头文件 | #include <pthread.h> |
原型 | int pthread_cond_broadcast(pthread_cond_t *cond); |
参数 | cond:条件变量 |
返回值 | 成功:返回0 失败:返回错误号 |
备注 | 先睡眠的线程先被唤醒(类似排队)。 |
功能 | 销毁条件变量 |
头文件 | #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
运行结果: