线程基本概念:
1)什么是线程
线程是进程中的一个独立控制流,由环境(包括寄存器集和程序计数器)和一系列要执行的指令组成。
2)与进程的联系:
1.进程是资源分配的最小单位,线程是系统调度的最小单位。
2.所有进程至少有一个线程组成,多线程的进程可以包括多个线程。
3.在一个多线程应用程序中,有多个执行部分同时执行,但是操作系统并没有把多个线程看成是多个独立的应用程序。
4.线程不能独立执行,必须依存在进程上,一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行,当进程中的一个线程崩溃,其他所有线程也崩溃。
5.两者的最大区别在于资源共享上,线程共享为进程分配的公共地址空间`,线程基本不拥有系统资源,只拥有少量在运行中必不可少的资源:如程序计数器,一组 寄存器,栈,线程信号掩码,局部线程变量和线程私有数据。
3)优缺点:
进程在使用时占用了大量的内存空间,特别是在进程线程间通信是必须借助操作系统提供的通信机制,这使得进程有自身的弱点;而线程占用资源少。使用灵活,应用程序多使用多线程而较少多进程;但是,进程之间不共享地址空间,彼此隔离性好,更安全,线程的层次关系和执行顺序并不明显。
基本操作(posix pthread API)
1)创建线程
使用函数pthread_create()来创建一个新的线程。函数声明如下:
extern int pthread_create (pthread_t *_restrict _newthread,
_const pthread_attr_t *_restrict _attr,
void *(*_start_routine) (void *),
void *_restrict _arg)
第一个参数用来存储线程ID,为指向线程ID的指针。若创建成功,则在此参数中返回ID,如果设为NULL,则不会返回生成的线程的标识符。pthread_t是unsigned long int
第二个参数用来设置线程的属性,主要设置与栈相关的属性,一般设为NULL,则新的线程使用系统默认的属性。
第三个参数是线程运行的代码起始位置,也即是线程执行的函数入口,通常为用户自定义。
第四个参数是运行函数的参数地址,如果自定义的函数要传入多个参数,则可传入一个结构体地址。
若创建成功,线程将拥有自己的形成属性和执行栈,并从调用程序那里继承信号掩码和调试优先级。函数如果执行成功返回0,失败,将返回非0值。pthread_create_exp.c:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct message
{
int i;
int j;
};
void *hello(struct message *str){
printf("the arg.i is %d\n",str->i);
printf("the arg.j is %d\n",str->j);
}
int main(int argc,char *agrc[]){
struct message test;
pthread_t thread_id_1;
test.i=10;
test.j=20;
pthread_create(&thread_id_1,NULL,(void *)*hello,&test);//create thread
printf("the new thread id is %u\n%u\n",thread_id_1); //print thread-id with "%u"
pthread_join(thread_id_1,NULL); //main thread wait son_thread's finish
}
2)线程退出与等待
退出:
新创建的线程从用户自定义的函数处开始执行,当遇到以下四种情况时退出:
1.调用pthread_exit 函数退出。
2.调用pthread_cancel 函数取消该线程。
3.创建线程的进程退出或者整个函数结束。
4.其中一个线程执行了exec类函数执行了新的进程。
Pthread库线程退出函数声明如下:
extern void pthread_exit (void *_retval)
此函数可以结束一个线程,结束方式与进程调用exit()函数类似。唯一的一个参数用来保存线程退出状态。
等待:
Pthread库使用pthread_join函数来作为线程等待函数,此函数在主线程(一般为创建另一个线程的线程)调用,用于等待子线程结束,类似于进程还总的wait()函数,执行成功返回0,失败返回非0。声明如下:
extern int pthread_join (pthread_t _th,void **_thread_return);
此函数会阻塞调用调用当前线程的进程,知道此线程结束。
第一个参数为被等待的线程ID,此线程必须同调用他的进程相联系,即不能在创建此线程时指明他为独立线程。
第二个参数是用户自定义的指针,用来存储被等待线程的返回值,如果pthread_create 的第二个参数设为NULL,则此退出的状态会丢失。
要设立某个独立线程可以调用pthread_detach()函数,调用成功,则此线程会与进程进行分离,成为一个独立的线程,并返回0,此线程结束时,系统会自动回收他的资源。声明:
extern int pthread_detach(pthread_t _th)
线程退出时仅会回收该线程的私有信息,在该线程中申请的堆空间并不是此线程的私有信息,在同一个进程中的线程可以访问。
退出线程示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void *helloworld(char *canshu){
int *p;
p=(int*)malloc(10*sizeof(int));
printf("the message is %s\n",canshu);
printf("the child id is %lu\n",pthread_self());
memset(p, 'c', 10);
printf("p=%x\n",p);
pthread_exit(p);
}
int main(int argc,int argv[]){
int error;
int *temp;
pthread_t thread_id;
pthread_create(&thread_id,NULL,(void*)*helloworld,"helloworld");
printf("*p=%x,p=%x\n",*helloworld,helloworld);
if(error=pthread_join(thread_id,(void**)&temp)){
perror("pthread_join");
exit(EXIT_FAILURE);
}
printf("temp=%x,*temp=%c\n",temp,*temp);
*temp='d';
printf("%c\n",*temp);
free(temp);
return 0;
}
以上介绍的是posix线程,在linux中将其作为一个库来使用,因此要在编译时需要加上-lpthread 以显示要链接的库。
线程退出前的操作:
类似于进程的atexit()函数,线程在退出前也可以执行用户显示定义的函数。
为什么要在线程退出前有操作?考虑这样一种情况:线程为了访问临界取资源而为其上锁,但在访问中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该线程在释放临界区资源时就被取消,那么临界区资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此需要一个机制来简化用于资源释放的编程。
pthread_cleanup_push()/pthread_cleanup_pop()函数用于自动释放资源,声明如下:
void pthread_cleanup_push(void (*routine) (void *),void *arg)
void pthread_cleanup_pop(int execute)
参数void (*routine) (void *arg)在调用pthread_cleanup_push()时压入清理函数栈,多次调用pthread_cleanup_push()将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈相反顺序弹出。
参数execute表示执行到pthread_cleanup_pop()时,是否在弹出清理函数的同时执行该函数,0表示不执行,非0表示执行,此参数并不影响异常终止时清理函数的执行。代码示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void cleanup(){ //线程清理函数,在子线程取消退出前执行
printf("cleanup\n");
}
void *test_cancel(void){
pthread_cleanup_push(cleanup, NULL);
printf("test_cancel\n");
while(1){ //子线程进入死循环,不在父线程中调用取消函数时将一直执行
printf("test message\n");
sleep(1);
}
pthread_cleanup_pop(1); //出栈并执行
}
int main(int argc,int argv[]){
pthread_t tid;
pthread_create(&tid,NULL,(void *)test_cancel,NULL); //创建线程
sleep(2);
pthread_cancel(tid); //取消子线程
pthread_join(tid,NULL); //等待子线程
}
结果中”cleanup“就是被取消时将执行入栈的操作的结果。
线程睡眠:
调用sleep(),sleep(2000)表示睡眠两秒。
取消线程:
取消线程指的是取消一个正在执行线程的操作。一个线程能过被取消并终止执行需要满足:
1.该县程是否可以被其他取消是可以设置的,linux下,默认可以被取消,可用宏分配是PTHREAD_CANCEL_DISABLE和PTHREAD_CANCEL_ENABLE;
2.该线程处于可取消点才可以被取消,即该线程被设为可以取消状态,另一个线程发起取消操作,该线程并不一定立即终止,只能在取消点才中止执行。可设置为立即取消和在取消点取消。用宏PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS.
取消函数pthread_cancel 用来向线程发起取消操作,声明如下:
extern int pthread_cancel(pthread_t _cancelthread)
设置可取消状态:
pthread_setcancelstate()用来设置可取消状态,声明如下:
extern int pthread_setcancelstate (int _state, int *_oldstate)
参数_state为调用线程的可取消状态所要设置的值;_oldstate为存储调用线程原来的可取消性状态的内存地址。_state设为PTHREAD_CANCEL_DISABLE,则针对目标线程的的取消请求处于未决状态,启用取消后才执行取消请求。若设为PTHREAD_CANCEL_ENABLE,则针对目标线程的取消请求将被传递。默认情况下,新创建的线程为PTHREAD_CANCEL_ENABLE.
设置取消类型:
pthread_setcanceltype()函数用于设置取消类型,即允许取消的线程在接收到取消操作后是立即终止还是到取消点终止,声明如下:
extern int pthread_setcanceltype (int _type,int *_oldtype)
参数_type若设为PTHREAD_CANCEL_ASYNCHRONOUS,则可以随时执行新的或者未决的取消请求;若设为PTHREAD_CANCEL_DEFERRED,则目标线程在到达一个取消点之前,取消请求将一直处于未决状态。
以上两个函数若执行成功,则将返回0,否则将返回错误编号以指明错误。
线程与私有数据:
多线程程序中,常要用全局变量实现多个函数间共享数据,由于数据空间是共享的,所以全局变量也是线程间共享的。但有时程序中需要提供线程私有的全局变量,仅在某个线程中有效,但可以跨多个函数访问,这时可以使用私有数据(TSD)
1.创建与注销私有数据:
使用函数pthread_key_create()创建和pthread_key_delete()撤销,声明如下:
int pthread_key_create (pthread_key_t *key, void (*destr_function) (void *)) //创建私有数据
int pthread_key_delete(pthread_key_t key) //撤销私有数据
此函数从TSD池中分配一项,将地址赋给key;若第二个参数不为NULL,线程退出时(调用pthread_exit())时将以key所关联的数据为参数调用其指向的函数,以释放分配的缓冲区。不管哪个线程调用的此函数,创建的key都是所有线程可访问的,但各个线程可以根据自己的需要往key中添值,相当与有了个同名但不同值的全局遍历。
2.读写线程私有数据:
要通过专门的posix函数来读写TSD,声明如下:
int pthread_setspecific(pthread_key_t key, const void *pointer)
void *pthread_getspecific(pthread_key_t key)
pthread_setspecific()将pointer的值与key相关联;第二个函数将数据读出来,返回值是void *类型,所以可以指向任何数据类型的数据,示例:
#include <pthread.h>
#include <stdio.h>
pthread_key_t key;
void echomsg(void *t){
printf("destructor excuted in thread %lu,param=%lu\n",pthread_self(),((int *)t));
}
void * child1(void* arg){
int i=10;
int tid=pthread_self();
printf("\nset key value %d int thread %lu\n",i,tid);
pthread_setspecific(key, &i);
printf("thread one sleep 2 until thread two finish\n");
sleep(2);
printf("\nthread %lu returns %d,add is %lu\n",tid,*((int *)pthread_getspecific(key)),(int*)pthread_getspecific(key));
}
void * child2(void *arg){
int temp=20;
int tid=pthread_self();
printf("\nset key value %d in thread %lu\n",temp,tid);
pthread_setspecific(key, &temp);
sleep(1);
printf("thread %lu returns %d,add is %lu\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key));
}
int main(void){
pthread_t tid1,tid2;
pthread_key_create(&key,echomsg);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_key_delete(key);
return 0;
}