1.互斥量:Mutex
多线程同一时间在同一块内存区域操作数据,防止数据的不一致. 挣用或者竞争情况 通常发生在多线程,执行的操作在相同的内存区域,比如修改同一个状态变量。
锁主要是锁住共享资源, 对于多线程访问的全局变量,需要添加锁。
1)POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。有四个值可供选择:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
主要用到的信号量函数有:
sem_init:初始化信号量sem_t,初始化的时候可以指定信号量的初始值,以及是否可以在多进程间共享。
sem_wait:一直阻塞等待直到信号量>0。
sem_timedwait:阻塞等待若干时间直到信号量>0。
sem_post:使信号量加1。
sem_destroy:释放信号量。和sem_init对应。
#ifndef _TLOCK_H #define _TLOCK_H #pragma once #include <pthread.h> class TLock { friend class TCond; // 互斥量与条件一起用 public: TLock(); virtual ~TLock(); // 可能作为基类,所以析构函数定义为virtual void Lock() // 上锁 { pthread_mutex_lock(&m_mutex); } void Unlock() // 解锁 { pthread_mutex_unlock(&m_mutex); } inline bool Trylock() // 尝试上锁 { return (pthread_mutex_trylock(&m_mutex) == 0); } protected: pthread_mutex_t m_mutex; // 互斥锁变量 }; class TCond { public: TCond() { pthread_cond_init(&m_cond, NULL); // 初始化一个条件变量 } ~TCond() { pthread_cond_destroy(&m_cond); // 释放一个条件变量 } void Signal() { pthread_cond_signal(&m_cond); // 条件改变,发送信号,唤醒单个线程 } void Boardcast() { pthread_cond_boardcast(&m_cond); // 条件改变,唤醒全部线程 } void Wait(TLock *plock) // 传入TLock 的 mutex { pthread_cond_wait(&m_cond, &plock->m_mutex); // 使线程阻塞在一个条件变量上,用于等待条件发生 } void Wait(TLock *plock, int seconds) { timespec tv; tv.tv_nsec = 0; tv.tv_sec = seconds; if(pthread_cond_timedwait(&m_cond, &plock->m_mutex, &tv) == 0) // 功能同上,只是等待超时的时候返回一个错误值ETIMEDOUT return true; else return false; } private: pthread_cond_t m_cond; }; // TGuard 直接传一个TLock进来,然后定义一个临时变量,即可实现上锁. class TGuard { public: TGuard(TLock &lock) : m_lock(lock) { m_lock.Lock(); } ~TGuard() { m_lock.Unlock(); } protected: TLock& m_lock; // 这里是引用 }; #endif
// TLock.cpp #include "TLock.h" static bool attr_initalize = false; static pthread_mutexattr_t attr; TLock::TLock() { attr_initalize = false; if(!attr_initalize) { pthread_mutexattr_init(&attr); // 如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); // pthread_mutexattr_settype 用来设置互斥锁属性 // PTHREAD_MUTEX_RECURSIVE_NP 嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 attr_initalize = true; } //int pthread_mutex_init( // pthread_mutex_t *mutex, // const pthread_mutexattr_t *mutexattr); //其中mutexattr用于指定互斥锁属性 pthread_mutex_init(&m_mutex, &attr); } TLock::~TLock() { pthread_mutex_destroy(&m_mutex); }
例子1: mutex的使用
// 使用实例:main.cpp #include <stdio.h> #include <stdlib.h> #include <iostream> using namespace std; #include "TLock.h" void *fnThread1(void *param) { TLock *plock = (TLock *)param; plock->Lock(); printf("thread1 Lock.\n"); for(int i=0; i<10; ++i) { cout<<"1"<<endl; sleep(1); } plock->Unlock(); printf("fnThread1 UnLock.\n"); return NULL; } void *fnThread2(void *param) { usleep(10); // 放弃CPU调度,目的让线程1先运行 TLock *plock = (TLock *)param; plock->Lock(); printf("fnThread2 Lock.\n"); for(int i=0; i<10; ++i) { cout<<"2"<<endl; sleep(1); } plock->Unlock(); printf("fnThread2 UnLock.\n"); return NULL; } int main(int argc, char ** argv) { pthread_t thread1, thread2; TLock mylock; pthread_create(&thread1, NULL, fnThread1, (void *)&mylock); pthread_create(&thread2, NULL, fnThread2, (void *)&mylock); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("main exit.\n"); getchar(); return 0; }
例子2: mutex, sem 编写的一个水池进水/出水 的例子
// 参考网址 //http://www.cnblogs.com/cs-jin-note/archive/2012/10/30/2746468.html // 三个线程 // 线程0 每次+5 in信号量减5 sem_wait // 线程1 每次+4 in信号量减4 sem_wait // 线程2 每次-3 out信号量减3 sem_post // 如果水池满 则阻塞进水线程 等待 #include <stdio.h> #include <stdlib.h> #include <semaphore.h> #include <errno.h>
#include <unistd.h> // sleep 头文件
#include <pthread.h> pthread_mutex_t g_mutex; sem_t poolin; // 水池容量 信号量 sem_t poolout; // 当前水池容量 信号量 // 线程1 加水 5 void *fnThread1(void *param) { while(1) { int rv = 0; for(int i=0; i<5; ++i) { while( ((rv = sem_wait(&poolin)) != 0) && (errno == EINTR) ); // poolin 信号量减去5 } pthread_mutex_lock(&g_mutex); // 上锁 (*(int *)param) += 5; printf("thread1: %d.\n",*(int*)param); for(int i=0; i<5; ++i) { sem_post(&poolout); // poolout 信号量加5 } pthread_mutex_unlock(&g_mutex); // 解锁 sleep(1); } return NULL; } // 线程2 加水 4 void *fnThread2(void *param) { while(1) { int rv = 0; for(int i=0; i<4; ++i) { while( ((rv = sem_wait(&poolin)) != 0) && (errno == EINTR) ); } pthread_mutex_lock(&g_mutex); (*(int *)param) += 4; printf("thread2: %d.\n",*(int *)param); for(int i=0; i<4; ++i) { sem_post(&poolout); } pthread_mutex_unlock(&g_mutex); sleep(1); } return NULL; } // 线程3 放水 -3 void *fnThread3(void *param) { while(1) { int rv = 0; for(int i=0; i<3; ++i) { while( ((rv = sem_wait(&poolout)) != 0) && (errno == EINTR) ) printf("xx"); } pthread_mutex_lock(&g_mutex); (*(int *)param) -= 3; printf("thread3: %d.\n",*(int *)param); for(int i=0; i<3; ++i) { sem_post(&poolin); } pthread_mutex_unlock(&g_mutex); sleep(1); } return NULL; } int main(int argc, char **argv) { pthread_t th[3]; int value = 0; // 初始化信号量 sem_init(&poolin, 0, 100); // 水池容量初始化为100 sem_init(&poolout, 0, 0); // 水池当前容量初始化为0 pthread_create(&th[0], NULL, fnThread1, (void *)&value); pthread_create(&th[1], NULL, fnThread2, (void *)&value); pthread_create(&th[2], NULL, fnThread3, (void *)&value); // 等待线程退出 for(int i=0; i<3; ++i) pthread_join(th[i], NULL); return 0; }
// 输出结果. 线程永远不会退出 3个线程的执行顺序是一直在变化的 [root@localhost test2]# make g++ -g -o test main.cpp -lpthread -lrt [root@localhost test2]# ./test thread2: 4. thread3: 1. thread1: 6. thread3: 3. thread2: 7. thread1: 12. thread3: 9. thread2: 13. thread1: 18. thread3: 15. thread2: 19. thread1: 24. thread3: 21. thread2: 25. thread1: 30. thread1: 35. thread2: 39. thread3: 36. thread1: 41. thread2: 45. thread3: 42. thread1: 47. thread2: 51. thread3: 48. thread1: 53. thread2: 57. thread3: 54. thread1: 59. thread2: 63. thread3: 60. thread1: 65. thread2: 69. thread3: 66. thread1: 71. thread2: 75. thread3: 72. thread1: 77. thread2: 81. thread3: 78. thread1: 83. thread2: 87. thread3: 84. thread1: 89. thread2: 93. thread3: 90. thread3: 87. thread2: 91. thread1: 96. thread3: 93. thread2: 97. thread3: 94. thread3: 91. thread2: 95. thread1: 100. thread3: 97. thread3: 94. ...
3.sem与mutex编写的队列Deque
//参考网址:http://www.cnblogs.com/liuweijian/archive/2009/12/30/1635888.html #include <iostream> #include <semaphore.h> #include <time.h> // #include <errno.h> // errno EINTR #include <deque> using namespace std; #include "TLock.h" //#include <pthread.h> template<typename T> class SemDeque { public: SemDeque(int maxsize) :m_MaxSize(maxsize) { sem_init(&m_enques, 0, maxsize); // 队列初始化容量为 maxsize sem_init(&m_deques, 0, 0); // 队列当前容量初始化为0 } ~SemDeque() { sem_destroy(&m_enques); sem_destroy(&m_deques); } int sem_wait_time(sem_t *psem, int mswait) { if(mswait < 0) // 无限等待 { int rv = 0; while( ((rv = sem_wait(psem)) != 0) && (errno == EINTR) ); // 等待信号量,errno==EINTR屏蔽其他信号事件引起的等待中断 return rv; } else { timespec tv; clock_gettime(CLOCK_REALTIME, &tv); // 获取当前时间 tv.tv_sec += mswait/1000; // 秒 tv.tv_nsec += (mswait%1000)*1000; // 纳秒 int rv = 0; while( ((rv = sem_timedwait(psem, &tv)) != 0) && (errno == EINTR) ); // sem_timedwait 等待信号量,errno==EINTR屏蔽其他信号事件引起的等待中断 return rv; } } bool push_back(const T& item, int mswait = -1) { if(-1 == sem_wait_time(&m_enques, mswait)) { return false; } m_lock.Lock(); // 上锁 m_data.push_back(item); cout<<"push"<<item<<endl; sem_post(&m_deques); // 当前队列信号量 加1 m_lock.Unlock(); // 解锁 return true; } bool pop_front(T &item, int mswait = -1) // item作为引用,是要传出来的值 { if(-1 == sem_wait_time(&m_deques, mswait)) { return false; } m_lock.Lock(); // 上锁 item = m_data.front(); cout<<"pop"<<item<<endl; m_data.pop_front(); sem_post(&m_enques); // 入队列信号量 加1 m_lock.Unlock(); // 解锁 return true; } size_t size() { return m_data.size(); } private: deque<T> m_data; TLock m_lock; int m_MaxSize; sem_t m_enques; // 入队列。 sem_t m_deques; // 出队列 };
// main.cpp #include "semDeque.h" #include <stdio.h> // printf SemDeque<int> deq(5); // 最多队列里只能有5个 void *pushThread(void *ptr) { for(int i=1; i<=30; ++i) { deq.push_back(i, -1); }
printf("push Thread exit.\n"); return NULL; } void *popThread(void *ptr) { while(1) { int a = 0; if( !deq.pop_front(a,1000) ) { cout<<"pop failed. size = "<<deq.size()<<endl; } } return NULL; } int main(int argc, char **argv) { pthread_t th[2]; pthread_create(&th[0], NULL, pushThread, NULL); pthread_create(&th[1], NULL, popThread, NULL); pthread_join(th[0], NULL); pthread_join(th[1], NULL); return 0; }
// 输出结果 popThread线程一直不会停止 (每次入队列5个 再出队列5个==>导致这个原因是因为push时输入的时间是-1,直接压入,而pop是要经过1s的延时才输出,所以看到的现象是5个进5个出,假如把时间都改成-1,进出的顺序就不是这样了。) [root@localhost test3]# make g++ -g -c TLock.cpp -lpthread g++ -g -c main.cpp -lpthread -lrt g++ -g -o test TLock.o main.o -lpthread -lrt [root@localhost test3]# ./test push1 push2 push3 push4 push5 pop1 pop2 pop3 pop4 pop5 push6 push7 push8 push9 push10 pop6 pop7 pop8 pop9 pop10 push11 push12 push13 push14 push15 pop11 pop12 pop13 pop14 pop15 push16 push17 push18 push19 push20 pop16 pop17 pop18 pop19 pop20 push21 push22 push23 push24 push25 pop21 pop22 pop23 pop24 pop25 push26 push27 push28 push29 push30 pop26 pop27 pop28 pop29 pop30 pop failed. size = 0 pop failed. size = 0 pop failed. size = 0 ...
线程get_thread每隔1000毫秒从队列取元素,线程put_thread将30个元素依次入队。两个线程模拟两条入队和出队的流水线。因我们在SemDeque<int> qq(5)处定义了队列最多可容纳5个元素,入队线程每入队到队列元素满5个后需阻塞等待出队线程将队列元素出队才能继续。测试时可调整队列可容纳最大元素个数来观察运行效果。
4.cond与mutex的配合使用
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
假设有共享的资源sum,与之相关联的mutex 是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可.每个线程的代码将像这样
add() { pthread_mutex_lock(lock_s); sum++; pthread_mutex_unlock(lock_s); }
如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零. 这种情况下,如果只用mutex, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum& amp; amp; amp; lt;100,则unlock,并sleep()本线程合适的一段时间. 这个时候,t0,t1,t2的代码不变,t3的代码如下:
print() { while { pthread_mutex_lock(lock_s); if(sum>=100) { printf(“sum reach 100!”); pthread_mutex_unlock(lock_s); } else { pthread_mutex_unlock(lock_s); my_thread_sleep(100); return ; } } }
这种办法有三个问题
1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep().这浪费了CPU处理时间.
2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t3才醒过来.
3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊!
你首先定义一个condition variable.
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER;
t0,t1,t2的代码只要后面加两行,像这样:
add() { pthread_mutex_lock(lock_s); sum++; pthread_mutex_unlock(lock_s); if(sum>=100) pthread_cond_signal(&cond_sum_ready); // 符合条件>=100,唤醒线程 }
t3线程:
print { pthread_mutex_lock(lock_s); while(sum<100) pthread_cond_wait(&cond_sum_ready, &lock_s); // 等待满足条件 printf(“sum is over 100!”); sum=0; pthread_mutex_unlock(lock_s); return; }
注意两点:
1) 在thread_cond_wait()之前,必须先lock相关联的mutex, 因为假如目标条件未满足,pthread_cond_wait()实际上会unlock该mutex, 然后block(阻塞),在目标条件满足后再重新lock该mutex, 然后返回.
2) 为什么是while(sum<100),而不是if(sum<100) ?这是因为在pthread_cond_signal()和pthread_cond_wait()返回之间,有时间差,假设在这个时间差内,还有另外一个线程t4又把sum减少到100以下了,那么t3在pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.
这就是用 while的用意。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int a = 0; // 线程1 负责叠加a的值 当a满足条件时唤醒线程2 void *fnThread1(void *ptr) { printf("enter thread 1.\n"); while(1) { pthread_mutex_lock(&mutex); a++; printf("a = %d\n",a); pthread_mutex_unlock(&mutex); if(a >= 100) { pthread_cond_signal(&cond); // 条件唤醒 break; } } printf("exit thread 1.\n"); return NULL; } // 线程2 当a不满足条件时则 条件阻塞。 void *fnThread2(void *ptr) { printf("enter thread 2.\n"); pthread_mutex_lock(&mutex); while(a < 100) { pthread_cond_wait(&cond, &mutex); } printf("a >= 100 conditions are met.\n"); pthread_mutex_unlock(&mutex); printf("exit thread 2.\n"); return NULL; } int main(int argc, char **argv) { pthread_t t1,t2; pthread_create(&t1, NULL, fnThread1, NULL); pthread_create(&t2, NULL, fnThread2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("main exit.\n"); return 0; }