一.什么是线程

  在一个程序里的多个执行路线就叫做线程。更准确的定义是:线程是“一个进程内部的一个控制序列”。

典型的unix进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程以后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每个线程处理各只独立的任务。

二.线程的优点

(1) 通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。

(2) 多个线程可以自动共享相同的存储地址空间和文件描述符。

(3) 有些问题可以通过将其分解从而改善整个程序的吞吐量。

(4) 交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中处理用户输入输出的部分与其它部分分开。

三.线程的缺点

    线程也有不足之处。编写多线程程序需要更全面更深入的思考。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。调试一个多线程程序也比调试一个单线程程序困难得多。

四.线程的结构

    线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽子,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存、栈以及文件描述符。

五.线程标识

    就像每个进程有一个进程ID一样,每个线程也有一个线程ID,进程ID在整个系统中是唯一的,但线程不同,线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread_t数据类型,所以可以移植的操作系统不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。

1.名称:pthread_equal

功能:比较两个线程ID

头文件:#include <pthread.h>

函数原形:int pthread_equal(pthread_t tid1,pthread_t tid2);

参数:tid1 进程1id, tid2 进程2id

返回值:若相等返回非0值,否则返回0

2.名称:pthread_self

功能:获取自身线程的id

头文件:#include <pthread.h>

函数原形:pthread_t pthread_self(void);

参数:无

返回值:调用线程的线程id

六.线程的创建  

3.名称:pthread_create

功能:创建线程

头文件:#include <pthread.h>

函数原形:int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);

参数:

返回值:若成功返回则返回0,否则返回错误编号

    当pthread_creat成功返回时, tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性。可以把它设置为NULL,创建默认的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。


#include <pthread.h>

void printids(const char *s)
{
printf(“%s pid:%u tid:%u \n“, getpid(),pthread_self());
}

void *thr_fn(void *arg)
{
printf (“new thread: “);
}

int main()
{
int err;
pthread_t tid;

err=pthread_create(&tid,NULL,thr_fn,NULL);
if(err=0)
printf(“can’t create thread:%s\n”,strerror(err));

printids(“main thread: “);
sleep(1);

exit(0);
}


关于进程的编译我们都要加上参数 –lpthread 否则提示找不到函数的错误

具体编译方法是 cc –lpthread –o gettid gettid.c

运行结果为

main thread: pid 14954 tid 134529024

new thread: pid 14954 tid 134530048

七.线程的终止

    线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。

(1)线程只是从启动例程中返回,返回值是线程的退出码。

(2)线程可以被同一进程中的其他线程取消。

(3)线程调用pthread_exit.

4.名称:pthread_exit

功能:终止一个线程 

头文件:#include <pthread.h>

函数原形:void pthread_exit(void *rval_ptr);

参数:rval_prt是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以调用pthread_join函数访问到这个指针。

返回值:无 

5.名称:pthread_join

功能:获得进程的终止状态

头文件:#include <pthread.h>

函数原形:int pthread_join(pthread_t thread,void **rval_ptr);

参数:

返回值:若成功返回0,否则返回错误编号。

    当一个线程通过调用pthread_exit退出或者简单地从启动历程中返回时,进程中的其他线程可以通过调用pthread_join函数获得进程的退出状态调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。

如果线程只是从它的启动历程返回,rval_ptr将包含返回码。


#include <pthread.h>
#include <string.h>

void *thr_fn1(void *arg)
{
printf(“thread 1 returning\n”);
return((void *)1);
}

void *thr_fn2(void *arg)
{
printf(“thread 2 exiting\n”);
return((void *)2);
}

int main()
{
pthread_t tid1,tid2;
void *tret;

pthread_create(&tid1,NULL,thr_fn1,NULL);
pthread_create(&tid2,NULL,thr_fn2,NULL);
pthread_join(tid1,&tret);

printf(“thread 1 exit code %d\n”,(int)tret);
pthread_join(tid2,&tret);
printf(“thread 2 exit code %d\n”,(int)tret);

exit(0);
}


结果是:

thread 1 returning

thread 2 exiting

thread 1 exit code 1

thread 2 exit code 2

附:注意点

(1) pthread_join 使一个线程等待另一个线程结束。

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

(2) 在编译的时候需要注意,由于线程创建函数在libpthread.so库中,所以在编译命令中需要将该库导入。命令如下:

gcc –o createthread –lpthread createthread.c

(3) 如果想传递参数给线程函数,可以通过其参数arg,其类型是void *。如果你需要传递多个参数的话,可以考虑将这些参数组成一个结构体来传递。另外,由于类型是void *,所以你的参数不可以被提前释放掉。

下面一个例子结合上面的内容:


int main ()
{
pthread_t thread1_id;
pthread_t thread2_id;

struct char_print_parms thread1_args;
struct char_print_parms thread2_args;

/* Create a new thread to print 30,000 x’s. */
thread1_args.character = ’x’;
thread1_args.count = 30000;
pthread_create (&thread1_id, NULL, &char_print, &thread1_args);

/* Create a new thread to print 20,000 o’s. */
thread2_args.character = ’o’;
thread2_args.count = 20000;
pthread_create (&thread2_id, NULL, &char_print, &thread2_args);

/* Make sure the first thread has finished. */
pthread_join (thread1_id, NULL);
/* Make sure the second thread has finished. */
pthread_join (thread2_id, NULL);

return 0;
}


(4)  线程属性

    在我们前面提到,可以通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我 们将这一类线程的属性称为detached。如果我们在调用pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线 程采用默认的属性,也就是joinable。如果需要将属性设置为detached,则参考下面的例子:


#include <stdio.h>
#include <pthread.h>
void * start_run(void * arg)
{
//do some work
}
int main()
{
pthread_t thread_id;
pthread_attr_t attr;

pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

pthread_create(&thread_id,&attr,start_run,NULL);
pthread_attr_destroy(&attr);

sleep(5);
exit(0);
}


     在线程设置为joinable后,可以调用pthread_detach()使之成为detached。但是相反的操作则不可以。还有,如果线程已经调用pthread_join()后,则再调用pthread_detach()则不会有任何效果。

(5) 前面提到线程可以通过自身执行结束来结束,也可以通过调用pthread_exit()来结束线程的执行。另外,线程甲可以被线程乙被动结束。这个通过调用pthread_cancel()来达到目的。当然,线程也不是被动的被别人结束。它可以通过设置自身的属性来决定如何结束

    线程的被动结束分为两种,一种是异步终结,另外一种是同步终结。异步终结就是当其他线程调用 pthread_cancel的时候,线程就立刻被结束。而同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式创建,那么它的属性是同步终结。

通过调用pthread_setcanceltype()来设置终结状态。

int pthread_setcanceltype(int type, int *oldtype);

state:要设置的状态,可以为PTHREAD_CANCEL_DEFERRED或者为PTHREAD_CANCEL_ASYNCHRONOUS

    那么前面提到的结束点又如何设置?最常用的创建终结点就是调用pthread_testcancel()的地方。该函数除了检查同步终结时的状态,其他什么也不做。     上面一个函数是用来设置终结状态的。还可以通过下面的函数来设置终结类型,即该线程可不可以被终结:

int pthread_setcancelstate(int state, int *oldstate);

state:终结状态,可以为PTHREAD_CANCEL_DISABLE或者PTHREAD_CANCEL_ENABLE。

(6) 线程的本质。

    在Linux中,新建的线程并不是在原先的进程中,而是系统通过 一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。