C++ 多线程 小记

来源:互联网 发布:影楼制作软件apk 编辑:程序博客网 时间:2024/05/20 18:45
1.定义线程
pthread_t pthreadhd;
2.定义线程函数
void *function(void *argc)
在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是全局的函数.在此声明是全局函数,不需要必须是static的,但是加上static好处多多,static起到什么作用,请自行补充知识点。3.创建线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 第一个参数:定义的线程句柄的地址 & (pthread_t)
  • 第二个参数:线程的属性地址 &(pthread_attr_t
  • 第三个参数:线程函数的地址 &(线程函数名字)
  • 第四个参数:线程函数的参数,线程函数的参数
所有参数都是指针。第一个参数: 不解释了,定义就好了。第二个参数: pthread_attr_t,主要包括线程的scope属性、detach属性、堆栈地址、堆栈大小、优先级等。在pthread_create时, 传递为NULL,表示使用默认参数。  pthread_attr_t介绍:pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:__detachstate,表示新线程是否与进程中其他线程脱离同步, 如果设置为PTHREAD_CREATE_DETACHED 则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。  为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_getXXX/pthread_attr_setXXX函数。在设置线程属性 pthread_attr_t 之前,通常先调用pthread_attr_init来初始化,之后来调用相应的属性设置函数。主要的函数如下:1、pthread_attr_init功能: 对线程属性变量的初始化。头文件: <pthread.h>函数原型: int pthread_attr_init (pthread_attr_t* attr);函数传入值:attr:线程属性。函数返回值:成功: 0  失败: -12、pthread_attr_setscope功能: 设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。头文件: <pthread.h>函数原型: int pthread_attr_setscope (pthread_attr_t* attr, int scope);函数传入值:attr: 线程属性。  scope:PTHREAD_SCOPE_SYSTEM,表示与系统中所有线程一起竞争CPU时间,  PTHREAD_SCOPE_PROCESS,表示仅与同进程中的线程竞争CPU函数返回值得:同1。3、pthread_attr_setdetachstate功能: 设置线程detachstate属性。该表示新线程是否与进程中其他线程脱离同步,如果设置为PTHREAD_CREATE_DETACHED则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。头文件: <phread.h>函数原型: int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);函数传入值:attr:线程属性。detachstate:PTHREAD_CREATE_DETACHED,不能用pthread_join()来同步,且在退出时自行释放所占用的资源  PTHREAD_CREATE_JOINABLE,能用pthread_join()来同步函数返回值得:同1。4、pthread_attr_setschedparam功能: 设置线程schedparam属性,即调用的优先级。头文件: <pthread.h>函数原型: int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);函数传入值:attr:线程属性。  param:线程优先级。一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0函数返回值:同1。5、pthread_attr_getschedparam功能: 得到线程优先级。头文件: <pthread.h>函数原型: int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);函数传入值:attr:线程属性;  param:线程优先级;第三个参数:线程函数地址,函数一定要是void *()(void *)格式,再取地址就是void (*)(void*)。第四个参数:对应线程函数的参数,就理解为线程函数的参数,是在这个位置传入。以上介绍了pthread_create()的各个参数的意思。需要注意pthread_creat后,如果没有错误,那就创建成功并且线程函数会开始执行。至于时序,请参照cpu的线程调度。4.运行等待int pthread_join(pthread_t thread, void **retval);例外:线程属性被设置PTHREAD_CREATE_DETACHED时无法使用该方法第一个参数:线程的句柄第二个参数:线程函数返回值的地址因为该方法是用来等待函数返回用的,当前调用线程会被阻塞,直至等待目标线程返回,资源被回收。有个粗浅的方法理解资源回收,例如:一个线程对某个公共资源上锁后操作,如果没有该线程返回,就强行退出,很有可能没有释放该公共资源的锁,导致其他线程无法访问。如果没有对公共资源的访问,可以不等待,但是自己评估可行性。5.线程KEYpthread_key_t __key; 

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量 一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。 和线程数据相关的函数主要有4个:

   1.  int pthread_key_create (pthread_key_t *__key, void (*__destr_function) (void *)):
创建一个键

 第一个参数:为指向一个pthread_key_t的指针

第二个参数:指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。
函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

   2.  int pthread_setspecific (pthread_key_t __key, const void *__pointer): 
为一个键指定线程数据
   
第一个参数:pthread_key_t
   
第二个参数:指向你想存储的数据的指针,任意类型的指针转成void*即可
   3.   void *pthread_getspecific (pthread_key_t __key)
从一个键读取线程数据
   第一个参数:pthread_key_t
   返回值是绑定到当前key上的数据,转换成存入时相应的类型即可。
   4.  int pthread_key_delete (pthread_key_t __key):
删除线程key
   第一个参数:pthread_key_t
删除当前的线程key。切记!!!不会自动释放你绑定到key上的数据相关的内存占用,也不会触发析构函数,如果你绑定的是类指针,需要自动释放。
 代码如下:
#include <unistd.h>#include <stdio.h>#include <pthread.h>#include <semaphore.h>//#include "multhread.h"pthread_key_t mthreadkey;int count = 0;int global_a = 100;void DestroyThreadKey(void *) {printf("DestroyThreadKey\n");        pthread_key_delete(mthreadkey);}void CreateThreadKey(void) {printf("CreateThreadKey\n");pthread_key_create(&mthreadkey, &DestroyThreadKey);}static void func() {int *pthreadkey_temp = reinterpret_cast<int *>(pthread_getspecific(mthreadkey));printf("func, pthreadkey_a = %d, global_a = %d threadid = %d\n",*pthreadkey_temp, global_a, pthread_self());}static void * threadfunc1(void *arg) {int pthreadkey_a = 1000;count ++;if (1 == count) {pthreadkey_a = 110;global_a = 110;} else if(2 == count) {pthreadkey_a = 220;global_a = 220;}static pthread_once_t once = PTHREAD_ONCE_INIT;pthread_once(&once, CreateThreadKey);char *pstr = reinterpret_cast<char *>(arg);printf("thread begin pstr = %s\n", pstr);while(1) {printf("thread process\n");break;}pthread_setspecific(mthreadkey, &pthreadkey_a);printf("function name threadfunc1, pthreadkey_a = %d, global_a = %d, pid = %d\n", pthreadkey_a, global_a, pthread_self());sleep(3);func();}int main(int argc, char const *argv[]){/* code *///multhread pmulthread;pthread_t pthreadhd1;pthread_t pthreadhd2;pthread_attr_t ppthread_attr_t;char str[] = "thread1";char str2[] = "thread2";pthread_create(&pthreadhd1, NULL, &threadfunc1, reinterpret_cast<void *>(str));pthread_create(&pthreadhd2, NULL, &threadfunc1, reinterpret_cast<void *>(str2));pthread_join(pthreadhd1, NULL);pthread_join(pthreadhd2, NULL);return 0;}
可以动手试试看结果,再理解下,应该会有所收获。
6.线程锁
 锁是为了防止对公共数据或者同一份代码访问时造成意想不到后果产生的。
  线程锁相关的方法主要有pthread_mutex_init,pthread_mutex_destory,pthread_mutex_lock,pthread_mutex_unlock。
 
pthread_mutex_init:锁初始化
 
pthread_mutex_destory:锁的销毁
 
pthread_mutex_lock:上锁
 
pthread_mutex_unlock:释放锁。
1,锁的创建
    锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    另外锁可以用pthread_mutex_init函数动态的创建,函数原型如下:
    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)

2,锁的属性
 
  互斥锁属性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr);来初始化,然后可以调用其他的属性设置方法来设置其属性;
    互斥锁的范围:可以指定是该进程与其他进程的同步还是同一进程内不同的线程之间的同步。可以设置为PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默认是后者,表示进程内使用锁。可以使用int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared)
pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared)
用来设置与获取锁的范围;
    互斥锁的类型:有以下几个取值空间:
  PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
  PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
    可以用以下方法操作线程属性:
    pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
    pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)

3,锁的释放
    调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
4,锁操作
    对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个。
  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_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待

5,锁的使用
锁的使用说简单也简单,说难也很难,死锁就是因为锁的使用不当引起的。切记尽量避免占用A锁的同时等待B锁,或者忘记释放锁。
有一个小窍门,写一个类,构造的时候调用上锁的方法,析构的时候调用释放锁的方法。参考如下代码。

#include <unistd.h>#include <stdio.h>#include <pthread.h>#include <semaphore.h>static pthread_mutex_t pth_mutex;class cpthread_mutx{public:cpthread_mutx(pthread_mutex_t *tmp_thread_mutex):thread_mutex(NULL) {thread_mutex = tmp_thread_mutex;pthread_mutex_lock(thread_mutex);printf("lock thread\n");}~cpthread_mutx() {pthread_mutex_unlock(thread_mutex);printf("unlock thread\n");}pthread_mutex_t *thread_mutex;};static void * threadfunc1(void *arg) {cpthread_mutx pcpthread_mutx(&pth_mutex);/*share codes or global value need protect*/}int main(int argc, char const *argv[]){/* code */pthread_t pthreadhd1;pthread_mutex_init(&pth_mutex, NULL);pthread_create(&pthreadhd1, NULL, &threadfunc1, NULL);pthread_join(pthreadhd1, NULL);pthread_mutex_destroy(&pth_mutex);return 0;}

7.条件变量
    在使用多线程的时候,肯定会遇到,某个线程可能需要等待一个状态,才继续运行。条件变量正是解决该问题的方法。    条件变量相关的有:pthread_cond_int,pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast, pthread_cond_timewait    1.条件变量初始化 int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
第一个参数:
cond是一个指向结构pthread_cond_t的指针,
第二个参数:
cond_attr是一个指向结构pthread_condattr_t的指针。
结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
2.等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)

等待条件有两种方式:条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
3.触发条件变量 
     int pthread_cond_signal(pthread_cond_t *__cond);
int 
pthread_cond_broadcast
(pthread_cond_t *__cond);
使用pthread_cond_signal不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
pthread_cond_broadcast会唤醒当前所有等待该条件变量的所有线程,他们参与竞争锁,最终哪个线程获得锁权限,由系统调度决定。
以下代码实现了
pthread_cond_timewait和
pthread_cond_signal的配合使用,broadcast可以自行尝试
#include <unistd.h>#include <stdio.h>#include <sys/time.h>#include <pthread.h>#include <semaphore.h>static pthread_mutex_t pth_mutex;static pthread_cond_t pth_cond;static pthread_key_t pth_key;static pthread_t pthreadhd1;static pthread_t pthreadhd2;class cpthread_mutx{public:cpthread_mutx(pthread_mutex_t *tmp_thread_mutex):thread_mutex(NULL) {thread_mutex = tmp_thread_mutex;pthread_mutex_lock(thread_mutex);printf("lock thread\n");}~cpthread_mutx() {pthread_mutex_unlock(thread_mutex);printf("unlock thread\n");}pthread_mutex_t *thread_mutex;};static void *threadfunc1(void *arg) {cpthread_mutx pcpthread_mutx(&pth_mutex);struct timespec ptime;struct timeval nowtime;gettimeofday(&nowtime, NULL);ptime.tv_sec = nowtime.tv_sec + 5;ptime.tv_nsec = nowtime.tv_usec * 1000;pthread_cond_timedwait(&pth_cond, &pth_mutex, &ptime);printf("thread name = %s \n", reinterpret_cast<char*>(arg));}static void *threadfunc2(void *arg) {cpthread_mutx pcpthread_mutx(&pth_mutex);pthread_cond_signal(&pth_cond);printf("thread name = %s \n", reinterpret_cast<char*>(arg));}int main(int argc, char const *argv[]){/* code */pthread_mutex_init(&pth_mutex, NULL);pthread_cond_init(&pth_cond, NULL);char *pthread_name1 = "thread1";pthread_create(&pthreadhd1, NULL, &threadfunc1, reinterpret_cast<void *>(pthread_name1));char *pthread_name2 = "thread2";pthread_create(&pthreadhd2, NULL, &threadfunc2, reinterpret_cast<void *>(pthread_name2));pthread_join(pthreadhd1, NULL);    pthread_join(pthreadhd2, NULL);pthread_mutex_destroy(&pth_mutex);return 0;}

8.信号量
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。
只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,
它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数:
sem_t:信号量的数据类型为结构sem_t,它本质上是一个长整型的数。
sem_init:函数sem_init()用来初始化一个信号量。它的原型为:extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
sem_post:函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
sem_wait:函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
sem_trywait:函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
sem_destroy:函数sem_destroy(sem_t *sem)用来释放信号量sem。
下面提供一个读取全局变量的例子:
#include <unistd.h>#include <stdio.h>#include <pthread.h>#include <semaphore.h>static sem_t pth_sem;static pthread_mutex_t pth_mutex;int data = 0;static void * threadread(void *arg) {while (1) {sem_wait(&pth_sem);pthread_mutex_lock(&pth_mutex);data--;printf("test data = %d\n", data);pthread_mutex_unlock(&pth_mutex);}}static void * threadwrite(void *arg) {while (1) {pthread_mutex_lock(&pth_mutex);data++;printf("test data = %d\n", data);pthread_mutex_unlock(&pth_mutex);sem_post(&pth_sem);}}int main(int argc, char const *argv[]){/* code */pthread_mutex_init(&pth_mutex, NULL);pthread_t pthreadhd1;pthread_t pthreadhd2;sem_init(&pth_sem, 0, 0);pthread_create(&pthreadhd1, NULL, &threadwrite, NULL);pthread_create(&pthreadhd2, NULL, &threadread, NULL);pthread_join(pthreadhd2, NULL);pthread_join(pthreadhd1, NULL);pthread_mutex_destroy(&pth_mutex);sem_destroy(&pth_sem);return 0;}


运行上面的结果会发现,打印出来的data值永远不会有负数。如果把sem_wait和sem_post注释掉,则会打印出负值。
sem用法的例子很多,例如仓库货物,当仓库有货物的时候才能提货,否则只能等待(sem_wait),而进货就用sem_post。总而言之一句话,sem_wait成功的次数一定不多于sem_post。
PS:尽量避免在thread中使用sleep,因为sleep会阻塞当前线程,无法打断,换用上面的pthread_cond_timewait最好了,可以参照上面代码。
以上是本人对pthread的粗浅理解,thread是很高深的,还希望大家多多指导,第一次写技术博客,格式比较烂,见谅!终!!
 
原创粉丝点击