1、为什么要有线程?
一个进程可以有多个线程,这个进程本身也叫做线程只不过是主线程。通常主线程分配任务给子线程做。程序设计时候就可以某一时刻不止做一件事情,每一个线程处理各自独立的任务。
- 多个线程自动可以访问相同的存储地址空间和文件描述符。
- 每个线程都包含有表示执行环境所必须的信息,其中包括线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字、errno变量及线程私有数据。一个进程所有信息对该进程所有线程共享,包括可执行代码、程序的全局内存和堆内存、栈以及文件描述符。
2、线程ID
每个线程都有一个线程ID,线程ID只有在它所属的进程上下文才有意义。
pthread_t//用于表示线程ID数据结构
pthread_equal (pthread_t __thread1, pthread_t __thread2);//用于比较线程ID
pthread_t pthread_self (void);//用户返回线程ID
//当线程需要识别以线程ID作为标识的数据结构时,pthread_self 函数可以与pthread_equal函数一起使用。主线程可能把工作任务放在一个队列中,用线程ID来控制每个工作线程和处理哪些作业,线程池的实现需要这两个函数。
3、线程创建
pthread_create()
/*
__newthread:函数成功返回将ID存储在此变量中。
__attr:定制线程属性。
__start_routine:函数指针。
__arg:传递给函数的参数。
*/
int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg);
pthread_t ntid;
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();//得到进程pid
tid = pthread_self();//得到当前线程pid,不能直接从内存读,可能在pthread_create没有返回就到了这一步,那么就不是正确的内容。
printf("%s pid=%lu , tid=%lu(0x%lx)\n" , s ,pid,tid,tid);
}
void *thr_fun(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
printids("new thread:");
return ( (void *)0 );//强制类型转换成返回指定类型.
}
int main(void)
{
int err;
err = pthread_create(&ntid , NULL , thr_fun ,NULL);
/*
参数一将返回进程ID存在tid里面.
参数二定制进程属性,后面讨论.
参数二进程从这个函数地址开始执行.
参数四,可以给参数三函数地址运行传参数.
*/
if(err != 0 )
err_exit(err,"can't creat thread");
printids("main thread:");//进程号称主进程
sleep(1);//因为线程创建不确定哪个先运行,防止主线程首先运行,退出了,那么子线程自然也退出了.
exit(0);
}
main thread: pid=14382 , tid=139681256101696(0x7f0a13a6d740)
new thread: pid=14382 , tid=139681238755072(0x7f0a129e2700)
解释了进程如何创建的过程。pid没有改变,每个进程有一个tid。
tid = pthread_self();//得到当前线程pid,不能直接从内存读,新线程可能在pthread_create没有返回就开始运行,那么通过ntid就得到没有初始化的内容。
4、线程终止
如果进程中的任意线程调用了exit、_Exit或者exit,那么整个进程就会终止。并且如果信号的默认动作是终止进程,那么收到该信号的进程也会终止。
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
- 线程可以简单地从启动例程中返回,返回值是线程的退出码。
- 线程可以被同一进程中的其他线程取消。
- 线程调用pthead_exit。
void pthread_exit (void *__retval);//指定返回值。
int pthread_join (pthread_t __th, void **__thread_return);
//等待指定的线程(th)终止,并获取进程终止状态(__thread_return)。状态是一个指针,所以得传递指针的指针。
thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
printf("thread 1 returning\n");
return ( (void *)1 );//强制类型转换成返回指定类型.
}
void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
printf("thread 2 exiting\n");
pthread_exit( (void *)2);
}
int main(void)
{
int err;
pthread_t tid1 , tid2;
void *tret;
err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 1");
err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 2");
err = pthread_join(tid1 , &tret);
if(err != 0)
err_exit(err , "cant't join thread 1");
printf("thread 1 exit code %ld\n" , (long)tret);//void *指针转换成整形
err = pthread_join(tid2 , &tret);//调用线程将一直阻塞,直到线程退出,
if(err != 0)
err_exit(err , "cant't join thread 1");
printf("thread 2 exit code %ld\n" , (long)tret);//void *指针转换成整形
exit(0);
}
thread 2 exiting
thread 1 returning
thread 1 exit code 1
thread 2 exit code 2
按 来关闭窗口…
可以通过退出函数返回一些重要的参数,也可以通过创建函数传入更加重要的参数。但是假如返回是局部变量,那么会出现问题。
struct foo{
int a , b , c ,d;
};
void printfoo(const char *str,struct foo *fp)
{
printf("%s" , str);
printf("struct foo at ox%lx\n" , (unsigned long)fp);
printf("foo.a = %d\n" , fp->a);
printf("foo.b = %d\n" , fp->b);
printf("foo.c = %d\n" , fp->c);
printf("foo.d = %d\n" , fp->d);
}
void *thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
struct foo foo = {1,2,3,4};//自动变量,线程退出,自动销毁
printf("thread 1 returning\n");
printfoo("thread 1:" , &foo);
pthread_exit ( (void *)&foo );//强制类型转换成返回指定类型.
//返回自动变量给主线程,会出现问题,因为会自动注销,所以不正确.
}
void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
printf("thread 2 exiting\n");
printf("thread 2 :id is %lu\n" , (unsigned long)pthread_self());
pthread_exit( (void *)2);
}
int main(void)
{
int err;
pthread_t tid1 , tid2;
struct foo *fp;
err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 1");
err = pthread_join(tid1 , (void **)&fp);//将其转换为指针的指针,注意这里必须是指针的指针.
if(err != 0)
err_exit(err , "cant't join thread 1");
printfoo("parent:\n" , fp);//打印从子线程返回的自动变量,肯定会有问题,因为局部变量内存以及释放了
sleep(1);//等待子线程优先执行完毕
err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 2");
sleep(1);//等待子线程优先执行.
exit(0);
}
thread 1 returning
thread 1:struct foo at 0x7fa7e653bef0
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
parent:
struct foo at 0x7fa7e653bef0
foo.a = 0
foo.b = 0
foo.c = -423819950
foo.d = 32679
thread 2 exiting
thread 2 :id is 140359100516096
当主线程访问这个结构时,结构的内容(在线程tid1的栈上分配的)己经改变了。注意第二个线程 (tid2) 的校是如何覆盖第一个线程的栈的。为了解决这个问题,可以使用全局结构,或者用malloc函数。
5、线程清理
简单就是线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。一个线程可以建立多个清理处理程序,处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。
pthead_clean_pop()//将清理函数压栈
pthead_clean_push()//将清理函数弹出栈,执行操作。
#include<stdio.h>
#include<pthread.h>
void
cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *
thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push( cleanup, (void *)"thread 1 first handler");
pthread_cleanup_push( cleanup, (void *)"thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
pthread_exit((void *)1);//线程退出,push进入的函数自动执行
pthread_cleanup_pop(0);//否则删除栈上的函数
pthread_cleanup_pop(0);
return((void *)1);
}
void *
thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, (void *)"thread 2 first handler");
pthread_cleanup_push(cleanup, (void *)"thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)//传入参数非0
pthread_exit((void *)2);//线程退出,push进入的函数自动执行
pthread_cleanup_pop(0);//否则删除栈上的函数
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
err_exit(err, "can't create thread 1");
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
err_exit(err, "can't create thread 2");
err = pthread_join(tid1, &tret);//等待1结束,并取得退出码
if (err != 0)
err_exit(err, "can't join with thread 1");
printf("thread 1 exit code %ld\n", (long)tret);//打印退出码
err = pthread_join(tid2, &tret);//等待2结束,并取得退出码
if (err != 0)
err_exit(err, "can't join with thread 2");
printf("thread 2 exit code %ld\n", (long)tret);//打印退出码
exit(0);
}
上述结果很清楚。
6、线程属性
暂时未用到,先忽略。
7、线程特定数据
线程特定数据称为线程私有数据,是存储和查询某个特定线程相关数据的一种机制。这样每个线程可以访问它自己单独的数据副本,而不需要担心与其他线程的同步访问问题。
在单线程程序中,我们经常要用到”全局变量”以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。 但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。POSIX线程库通过维护一定的数据结构来解决这个问题,这个些数据称为线程特定数据(Thread-specific Data,或 TSD)。
int pthread_key_create (pthread_key_t *__key,
void (*__destr_function) (void *));
/*
__key:创建的键存储在key指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址设为空值。也就是每个线程使用相同的键索引不同的线程特定数据空间,一般通过malloc分配。线程可以给特定数据分配多个键,也就是一个线程含有多个特定数据区域。
__destr_function:设置对应的析构函数,当线程返回或者pthread_exit时,如果其特定数据地址空间已经被置为非空值,那么析构函数就会被调用清理特定数据区域,由于分配通常用malloc如果不清理会造成内存泄漏。,线程取消时,只有在最后的清理处理程序返回之后,析构函数才会被调用。pthread_key_create的时候,必须保证多线程竞争的问题,防止创建失败。
*/
int pthread_key_delete (pthread_key_t __key);
//取消键与线程特定数据值关联关系。注意不会激活析构函数。
int pthread_setspecific (pthread_key_t __key, const void *__pointer)
/*
键一旦创建以后,就可以通过调用pthread_setspecific函数把键和线程特定数据关联起来。注意这个地址空间通常需要程序员手动malloc分配。
*/
void *pthread_getspecific (pthread_key_t __key);
/*
获得线程特定数据的地址,假如没有设置则返回NULL。可以用这个返回值来确定是否需要调用pthread_setspecific.
*/
下面通过getenv获取环境变量对象的值是线程安全的。通过pthread_once保证pthread_key_create仅仅初始化一次,然后将环境变量对象的数值全部存储在线程特定数据空间上。
#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define MAXSTRINGSZ 4096
static pthread_key_t key;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;
extern char **environ;
static void
thread_init(void)
{
pthread_key_create(&key, free);//析构函数利用free。因为我们打算用malloc分配
}
char *
getenv(const char *name)
{
int i, len;
char *envbuf;
pthread_once(&init_done, thread_init);
/*此函数表示如果当第一个线程调用它时会执行相应的函数,然后从pthread_once返回,而接下去的其他线程调用它时将不再执行thread_init函数,此举是为了只调用一次pthread_key_create,即产生一个pthread_key_t值,并且可以防止多线程竞争保证初始化一次。
*/
pthread_mutex_lock(&env_mutex);//加锁,
envbuf = (char *)pthread_getspecific(key);//获取特定数据,假如地址为空,那么重新malloc
if (envbuf == NULL) {
envbuf = malloc(MAXSTRINGSZ);
if (envbuf == NULL) {
pthread_mutex_unlock(&env_mutex);
return(NULL);
}
pthread_setspecific(key, envbuf);//设置malloc新的空间为当前线程私有数据。
}
len = strlen(name);
for (i = 0; environ[i] != NULL; i++) {
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == '=')) {
strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);//将环境变量对应值拷贝到线程私有空间
pthread_mutex_unlock(&env_mutex);
return(envbuf);
}
}
pthread_mutex_unlock(&env_mutex);
return(NULL);
}