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)
- 第三个参数:线程函数的地址 &(线程函数名字)
- 第四个参数:线程函数的参数,线程函数的参数
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(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));结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。第一个参数:cond是一个指向结构pthread_cond_t的指针,第二个参数:cond_attr是一个指向结构pthread_condattr_t的指针。
尽管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);intpthread_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是很高深的,还希望大家多多指导,第一次写技术博客,格式比较烂,见谅!终!!
- mfc多线程小记
- 多线程学习小记
- 多线程小记1
- Android 多线程小记
- 多线程java小记
- C++ 多线程 小记
- 多线程_小记
- java多线程ThreadLocal源码小记
- C基础知识点小记
- C#Winform小记
- c++primer学习小记
- C++Primer学习小记
- C#Winform小记
- 知识小记(C#)
- c代码小记
- object-c 学习小记
- C 打印格式小记
- 技术小记4(C++)
- 最小生成树---kruskal模板(并查集优化)
- linux vim互相复制粘贴
- 实现MFC 对话框最大化时控件也随比例最大化或者还原
- 访问单个节点的删除
- 关于sizeof运算符的文档
- C++ 多线程 小记
- php fsockopen()方法,简化,异步非阻塞调用
- DOM 2级事件模型跨浏览器处理
- 阿里云oss传输文件报错 Unsupported algorithm: HmacSHA1
- Makefile的规则
- apache commons工具类简介
- Hbase 之 HBase 的整体架构
- razor 页面 js int 输出 等
- Android波纹进度条 轻松地让它浪起来