多线程编程要使用的函数

2017-09-26  本文已影响0人  Joe_HUST

创建线程

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_function)(void *),void *restrict arg)
//成功返回0,其他值则出错
// 第一个参数为指向线程标识符的指针.第二个参数用来设置线程的属性,第三个参数是线程运行函数的起始位置,第四个参数是运行函数的参数

extern int pthread_join __p(pthread_t __th,void ** __thread_return);
//第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值.
//这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。

C99新增restrict用于限定指针;该关键字用于告诉编译器,所有修改该指针所指向的内容的操作全部都是基于该指针的,即不存在其它进行修改操作的途径;这样可以帮助编译器进行更好的代码优化.生成更有效率的汇编代码.在gcc中使用C99标准要加上 -std=C99;
例如:void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 这是一个很有用的内存复制函数,由于两个参数都加了restrict限定,所以两块区域不能重叠,即 dest指针所指的区域,不能让别的指针来修改,即src的指针不能修改. 相对应的别一个函数 memmove(void *dest,const void * src,size_t)则可以重叠。


线程取消

通常线程会在主体函数退出的时候自动终止,但是也可以因为收到另外一个线程发来的终止(取消)请求而强制终止.线程取消的方法是向目标线程发送cancel信号,但是如何处理cancel信号则是由目标线程自己决定的,可以忽略,立即终止,或者是继续运行到cancelation-point(取消点),由不同的cancelation状态决定.线程收到CANCEL信号的缺省状态是运行到取消点(pthread_create()创建线程的缺省状态).
取消点包括以下一些函数:pthread_join(),pthread_testcancel(),pthread_condition_wait(),pthread_cond_timewait(),sem_wait(),sigwait()等等函数,以及read(),write()等会引起阻塞的系统调用都是取消点.而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手 册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻 塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();
retcode = read(fd,buffer,length);
pthread_testcancel();

互斥锁

pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

互斥锁的属性:

  1. PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  2. PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  3. PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
  4. PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
锁的一些操作
  1. int pthread_mutxe_lock(pthread_mutex_t *mutex)
  2. int phread_mutex_unlock(pthread_mutex_t *mutex)
  3. int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

死锁

死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。总体来讲, 有几个不成文的基本原则:

  1. 对共享资源操作前一定要获得锁。
  2. 完成操作以后一定要释放锁。
  3. 尽量短时间地占用锁。
  4. 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
  5. 线程错误返回时应该释放它所获得的锁。

条件变量

#include <pthread.h>
pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *attr);
//成功返回0,其他的值表示错误

不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
//返回0表示成功,其他值表示失败

该函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。


pthread_cleanup_push()和pthread_cleanup_pop()的目的和作用

例如一个线程thread1:

pthread_mutex_lock(&mutex);
//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(......);            //这里是随便找的一个可以阻塞的接口
pthread_mutex_unlock(&mutex);

上例中若线程1执行了accept(),线程会阻塞(也就是等在那里,有客户端连接的时候才返回,或则出现其他故障),线程等待中......
这时候线程2发现线程1等了很久,不赖烦了,他想关掉线程1,于是调用pthread_cancel()或者类似函数,请求线程1立即退出。这时候线程1仍然在accept等待中,当它收到线程2的cancel信号后,就会从accept中退出,然后终止线程,注意这个时候线程1还没有执行:pthread_mutex_unlock(&mutex);也就是说锁资源没有释放,这回造成其他线程的死锁问题。
所以必须在线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面),pthread_cleanup_pushpthread_cleanup_pop就是用来做这样的工作的。

pthread_cleanup_push(some_clean_func,...)
pthread_mutex_lock(&mutex);
//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(......);            //这里是随便找的一个可以阻塞的接口
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
return NULL;

上面的代码,如果accept被cancel后线程退出,会自动调用some_clean_func函数,在这个函数中你可以释放锁资源。如果accept没有被cancel,那么线程继续执行,当pthread_mutex_unlock(&mutex);表示线程自己正确的释放资源了,而执行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函数。接着return线程就正确的结束了。
push进去的函数可能在以下三个时机执行:

  1. 显示的调用pthread_exit();
  2. 在cancel点线程被cancel。
  3. pthread_cleanup_pop()的参数不为0时。
上一篇下一篇

猜你喜欢

热点阅读