多线程总结下

来源:互联网 发布:数组大小可以任意改变 编辑:程序博客网 时间:2024/05/01 15:20
线程的取消:线程本身使用资源的释放
线程清理函数
线程资源(内存,文件描述符)保护,线程锁mutex
线程的同步,条件

perro  查看错误
以前很多函数返回值ret为 -1,系统将错误码存到全局变量errno(所以在用变量名时不能用它)中,perror函数读取变量errno,从而打印错误信息
而在posix 标准中只打印sucess,不会打印错误信息,因为posix没有将系统的错误码存到errno变量中,所以perror去读取该值时,errno默认为0,故
posix标准的pthread库函数:
1.没有修改全局变量errno(所以使用posix函数(即以pthread开头函数)时不要使用perror)
2.函数如果失败,返回值ret就是错误码的值(为正值)(不失败则返回0)
判断函数失败的方法:
判断ret是否为0,If不为0,打印ret的值,然后根据ret去查相应的错误


3.线程的等待退出
3.1.等待线程退出
 
如果每个线程都需要用到的变量,定义成全局变量

线程从入口点函数自然返回,或者主动调用pthread_exit()函数,都可以让线程正常终止
线程从入口点函数自然返回时,函数返回值可以被其它线程用pthread_join函数获取
这个用得比较少,因为一般线程创建之后是让其一直工作的,有时只是通过判断其返回检验是否执行到退出函数了
pthread_join原型为:
    #include <pthread.h>
    int pthread_join(pthread_t th, void **thread_return);
传入具体的Id,所以每次只能回收一个线程
第一个参数为线程ID,为传入参数,直接传整型数就行了
第二参数为二级指针,因为thread_return是exit(t)中的t是一级指针,故传二级指针,即
拿指针时定义一个(void*) p,然后将&p放到该位置
1. 该函数是一个阻塞函数,一直等到参数th指定的线程返回;与多进程中的wait或waitpid类似。
thread_return是一个传出参数,接收线程函数的返回值。如果线程通过调用pthread_exit()终止,则pthread_exit()中的参数相当于自然返回值,照样可以被其它线程用pthread_join获取到。

2.该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果th线程类型并不是自动清理资源类型的,则th线程退出后,线程本身的资源必须通过其它线程调用pthread_join来清除,这相当于多进程程序中的 waitpid

子线程退出时不会清理系统资源:(以下两图是在子进程中申请内存时,主进程可用)
我们malloc以后,得到p的地址:b6c00468  是申请的堆内存的首地址,这时系统同时记录了malloc空间的大小,所以
1.释放时必须把首地址给free函数。因此,如果需要对p进行偏移时,用中间量或(p+i)
2.free之后,p的值没变,还是b6c00468,因为通过man 知free(p)传入的是一级指针,所以每次free之后,都要将p置成NULL,不然成为野指针


3.3.线程终止清理函数
最经常出现的情形是资源独占锁的使用:线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数

API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg) 
routine为传入参数和返回值都是空的函数指针

void pthread_cleanup_pop(int execute) pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,即先压入的函数,后执行

void routine(void  *arg)函数在调用pthread_cleanup_push()时压入清理函数栈(每次只能压一个),多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链(即一个挨一个,先压入的在栈底),在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行,即都要弹出来,由其决定是否要执行该清理函数。这个参数并不影响异常终止时清理函数的执行。 
void *pthread_func(void *p)
{
     pthread_cleanup_push(a,(void*)1);//线程刚开始执行时,将清理函数压栈
     .........
     .........中间是线程代码
     pthread_clean_pop(1);
}

以下是应用场景:
1.push 进去,pop出来,执行清理

2.pop(0),执行到该位时弹出清理函数但不执行;但在pop之前,加入pthread_exit,就会执行清理函数。

3.线程被cancel,调用执行清理动作,原理与从pthread_exit()出来一样(在push执行完之后才执行cancel指令,即cancel点在push之后,if与pop之间没有缓冲时间,则会直接执行pop)(cancel函数要放在join之前,不然等到执行到cancel时子线程已经退出了,没意义)

因此,如果要申请内存,则将malloc放在push之前:

4.线程的同步与互斥
4.1.线程的互斥

Posix Thread中定义了一套专门用于线程互斥的mutex函数。mutex是一种简单的加锁的方法来控制对共享资源的存取,这个互斥锁只有两种状态(上锁和解锁),可以把互斥锁看作某种意义上的全局变量。为什么需要加锁,就是因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
挂起和阻塞的区别:只是为了描述不同场景下线程进入睡眠的不同词语。阻塞是因为read时还没有往标准输入输东西,则阻塞,这里为睡眠状态,当有输入才唤醒;而挂起是因为锁已经被别人锁了而睡眠

murtex主要的几个操作:
初始化锁,加锁,解锁,销毁锁

1. 创建和销毁锁

有两种方法创建互斥锁,静态方式动态方式

·静态方式:(基本不用)


POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER; 
等号左边为定义一个锁的变量
Linux Threads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个宏常量。

·动态方式:

动态方式是采用pthread_mutex_init()函数来初始化互斥锁API定义如下:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr)
&mutex,只有取地址后放进去,才能改变nutex中的值
 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。通常为NULL 
pthread_mutex_destroy()用于注销一个互斥锁API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此Linux Threads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
初始化锁:

 

2. 互斥锁属性
互斥锁属性结构体的定义为:
typedef struct
{
  int __mutexkind;//注意这里是两个下划线
} pthread_mutexattr_t;
 互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性__mutexkind,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同也就是是否阻塞等待。有三个值可供选择:
· 直接写NULL普通锁(或快速锁)当一个线程加锁以后,其余请求锁的线程将形成一个阻塞等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性
示例:初始化一个快速锁。
pthread_mutex_t  lock;
pthread_mutex_init(&lock, NULL);
·PTHREAD_MUTEX_RECURSIVE_NP嵌套锁允许同一个线程对同一个锁成功获得多次并通过多次unlock解锁如果是不同线程请求,则在加锁线程解锁时重新竞争
pthread_mutex_lock(&mutex);//写两次
pthread_mutex_lock(&mutex);
示例:初始化一个嵌套锁。
pthread_mutex_t  lock;
pthread_mutexattr_t  mutexattr;
mutexattr.__mutexkind = PTHREAD_MUTEX_RECURSIVE_NP;
pthread_mutex_init(&lock, &mutexattr);
·PTHREAD_MUTEX_ERRORCHECK_NP检错锁如果同一个线程请求同一个锁则返回EDEADLK否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。如果锁的类型是快速锁,一个线程加锁之后,又加锁,则此时就是死锁。

示例:初始化一个嵌套锁。

pthread_mutex_t  lock;

pthread_mutexattr_t  mutexattr;

mutexattr.__mutexkind = PTHREAD_MUTEX_ERRORCHECK_NP;


pthread_mutex_init(&lock, &mutexattr);

3.锁操作
锁操作主要包括
加锁  int pthread_mutex_lock(pthread_mutex_t *mutex)
解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex)
测试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex)
·pthread_mutex_lock:加锁,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁类型解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
·pthread_mutex_unlock:根据不同的锁类型,实现不同的行为:
对于快速锁,pthread_mutex_unlock解除锁定;
对于递规锁,pthread_mutex_unlock使锁上的引用计数减1
对于检错锁,如果锁是当前线程锁定的,则解除锁定,否则什么也不做。 
·pthread_mutex_trylock(尝试加锁):语义与pthread_mutex_lock()类似,当其返回0时,效果跟lock一样,表示已锁上,不同的是在锁已经被占据时返回EBUSY而不是挂起等待
0 0
原创粉丝点击