线程的优势
多进程与多线程
多进程,每个进程都有自己独立的地址空间;多线程,同一进程内的线程共享进程的地址空间。所以创建一个就要耗费时间来为其分配系统资源,而创建一个新线程花费的时间会少很多。
进程地址空间独立而线程共享地址空间,线程的切换速度远快于进程的切换速度
进程间数据空间相对独立,彼此间通信要以专门的通信方式进行,通信时必须经过操作系统。而同一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用。进程间通信更加方便和省时。
综上,线程节约时间和资源
此外,线程可以提高程序的响应速度,可以提高多处理器的效率,可以改善程序的结构
ps:编写linux下的多线程应用程序,需要头文件pthread.h,链接时需要使用库libpthread.a
gcc ×××.c -lpthread
线程的创建
pthread_create
#include<pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t * attr,
void* (*start_routine)(void *),void *arg);
pthread_t pthread_self(void) //获取本线程的ID
int pthread_equal(pthread_t thread1,pthread_t thread2).//判断两个线程ID是否指向同一线程
int pthread_once(pthread_once_t *once_control,void(* init_routine)(void)) //用来保证init_routine线程函数在进程中仅执行一次
pthread_create
thread:一个指针,线程创建成功时,用来返回创建线程的ID
attr:该参数用于指定线程属性,NULL表示默认属性,可以了解,需要用再查
start_routine:一个函数指针,指向线程创建后要调用的函数;这个被线程调用的函数也叫做线程函数。
arg:该函数指向传递给函数的参数
注意:创建成功,函数返回0;若不为0则说明创建线程失败。
线程的终止
1.通过return从线程函数返回
2.调用函数pthread_exit()使线程退出
3.调用pthread_cancel函数取消线程
在主线程中,如果从main函数返回或是调用exit函数退出主线程,则整个进程将终止,则整个进程终止,进程内的其他线程也会终止。若调用pthread_exit()函数,则仅仅是主线进程消亡,进程不会结束,其它线程也不会终止,直到所有线程结束,进程才会结束。
当进程要使用临界资源时,要提出请求,如果该资源未被使用则申请成功,否则等待。临界资源使用完毕后要释放以便其它线程使用。
临界资源为一个线程独占,当一个线程终止时,如果不释放其占有的临界资源,则该资源会被认为还被已经退出的线程所使用,因而永远不会得到释放,如果一个线程在等待使用这个临界资源,有可能无限制的等待,就形成来死锁。
利用pthread_cleanup_push(),pthread_cleanup_pop()函数可以自动释放资源
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。
采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
#include<pthread.h>
#define pthread_cleanup_push(routine,arg) \
{
struct _pthread_cleanup_buffer buffer; \
_pthread_cleanup_push(&buffer,(routine),(srg))\
#define pthread_clean_pop \
_pthread_cleanup_pop(&buffer,(exeute));
}
pthread_cleanup_push((void *)pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
本来do some work之后是有pthread_mutex_unlock(&mut);这句,也就是有解锁操作,但是在do some work时会出现非正常终止,那样的话,系统会根据pthread_cleanup_push中提供的函数,和参数进行解锁操作或者其他操作,以免造成死锁!
**attention:**pthread_cleanup_push()有’{‘,pthread_cleanup_pop()中有’}’,所以这两个函数要成对出现
一般情况下,进程中各个线程的运行是独立的,线程的终止不会相互通知,也不会影响其他线程,终止进程的所占有的资源不会随着线程的终止归还系统,仍为线程所在的进程拥有。
#include<pthread.h>
void pthread_exit(void * retval)
int pthread_join(pthread_t th,void * thread_return);
int pthread_detach(pthread_t th);
pthread_join()的调用者被挂起并等待th线程终止,如果thread_return不为NULL,则 *thread_return=retval,一个线程仅允许一个线程使用pthread_join等待它终止,并且被等待的进程应该处于可join状态,即非DETACHED。(如果有多个进程等待,第一个接受信号的成功返回,其余返回错误代码ESRCH)
一个可join的线程所占的内存仅当有线程对它执行pthread_join()后才会释放,
所以为了避免内存泄漏,所有线程终止时,要嘛已被设定为DETACHED,要嘛使用pthread_join()来回收资源。
pthread_cancel:来自其他线程的一个请求
但是如果子线程在某个操作被阻塞,等待它的线程会无限制的等待,形成死锁。为了更安全地使线程退出,主线程通过pthread_cancel函数来请求取消同一进程中的其他线程,再调用pthread_join等待指定线程退出。
注:当主线程调用pthread_cancel后,只是将取消请求发送给指定线程, 成功调用不能保证指定线程已经退出,需要调用pthread_join等待指定线程完全退出,再进行相关资源的释放。
可取消状态:当线程处于PTHREAD_CANCEL_ENABLE,收到cancel请求会使该线程退出运行;反之,若处于PTHREAD_CANCEL_DISABLE,收到的cancel请求将处于未决状态,线程不会退出。线程默认可取消状态为PTHREAD_CANCEL_ENABLE,可通过pthread_setcanceltype修改可取消类型。
可取消类型:当处于PTHREAD_CANCEL_DEFERRED,线程在收到cancel请求后,需要运行到取消点才能退出运行;如果处于PTHREAD_CANCEL_ASYNCHRONOUS,可以在任意时间取消,只要收到cancel请求即可马上退出。线程启动时默认可取消类型为PTHREAD_CANCEL_DEFERRED,可通过pthread_setcanceltype修改可取消类型。
取消点:线程检查是否被取消并按照请求进行动作的一个位置。
一般来说,为了防止资源未被释放时,线程就已经退出。将获取临界资源-释放临界资源之间的代码块都设置成PTHREAD_CANCEL_DISABLE状态,其余的代码块都设置成PTHREAD_CANCEL_ENABLE状态,确保线程在安全的地方退出。如果在可以安全退出的代码块不存在取消点系统调用,可以调用pthread_testcancel函数自己添加取消点。
线程中的私有数据
进程中内的所有线程共享进程的数据空间,因此全局变量为所有线程共有,在程序设计时有时需要保存线程自己的全局变量,这种变量仅在某个线程的内部有效。这时就要用到线程的私有数据(TSD),在线程内部,线程的私有数据可以被各个函数访问,但它对其它线程是屏蔽的。
在使用线程的私有数据时,首先要为每个线程创建一个相关联的键,一键多值,通过键来指代线程数据。
1、pthread_key_create:创建一个键
int pthread_key_create(pthread_key_t *key,void(*destr_function) (void*));
//第二个参数是一个析构函数
首先从linux的TSD池(Thread-specific Data)中分配一项,然后将其值赋给key供以后访问使用。接口的第一个参数是指向参数的指针,第二参数是函数指针,如果该指针不为空,那么在线程执行完毕退出时,已key指向的内容为入参调用destr_function(),释放分配的缓冲区以及其他数据。
key被创建之后,因为是全局变量,所以所有的线程都可以访问。各个线程可以根据需求往key中,填入不同的值,这就相当于提供了一个同名而值不同的全局变量,即一键多值。
一键多值依靠的一个结构体数组,即
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};
2、pthread_setspecific:为指定键值设置线程私有数据
int pthread_setspecific(pthread_key_t key, const void *pointer);
该函数将指针pointer的值(指针值而非其指向的内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。
3、pthread_getspecific:从指定键读取线程的私有数据
void * pthread_getspecific(pthread_key_t key);
通过该函数得到与key相关联的数据
4、pthread_key_delete:删除一个键
void * pthread_getspecific(pthread_key_t key);
该函数用于删除一个键,功能仅仅是将该key在结构体数组pthread_keys对应的元素设置为“un_use”,与该key相关联的线程数据是不会被释放的,即是仅仅将键删除,因此线程私有数据的释放必须在释放键之前完成。