1. 线程间通信-互斥锁
互斥锁,适用于共享资源只有一个的情况下。用简单的加锁方法控制对共享资源的原子操作
只有两种状态:上锁、解锁
可把互斥锁看作某种意义上的全局变量
在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作
若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
互斥锁保证让每个线程对共享资源按顺序进行原子操作。
互斥锁基本函数:
互斥锁初始化:pthread_mutex_init()
互斥锁上锁:pthread_mutex_lock()
互斥锁判断上锁:pthread_mutex_trylock()
互斥锁解锁:pthread_mutex_unlock()
消除互斥锁:pthread_mutex_destroy()
2. 线程间通信-信号量
Semaphores make it possible to have ahigher level of concurrency than mutexes. If accesses to the buffer wereguarded by a QMutex, the consumer thread couldn't access the buffer at the sametime as the producer thread. Yet, there is no harm in having both threadsworking on different parts of the buffer at the same time.
The example comprises two classes: Producerand Consumer.
适用于共享资源有多个的情况下,比如消费者-生产者。各自操作缓冲区不受影响。只要缓冲区有产品,消费者就可以消费;只要缓冲区有空余的,生产者就可以生产。如果使用互斥锁机制,则同一时刻,只能有一方消费或者生产,这是不科学的。
因此可定义两个信号量,一个用来限制消费者,一个用来限制生产者。消费者和生产者都是用pv操作来操作缓冲区资源。
信号量函数
sem_init()创建一个信号量,并初始化它
sem_wait()和sem_trywait(): P操作,在信号量大于零时将信号量的值减一
•区别:若信号量小于零时,sem_wait()将会阻塞线程,sem_trywait()则会立即返回
sem_post(): V操作,将信号量的值加一同时发出信号来唤醒等待的线程
sem_getvalue():得到信号量的值
sem_destroy():删除信号量
下面例子使用信号量机制实现生产者-消费者, buff模拟一个循环队列缓冲区。 消费者操作缓冲区头部即BuffHead, 生产者操作尾部即BuffTail
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <string.h>
#include "MySemaphore.h"
struct BuffFormat Buff[BUFF_NUM];
int BuffHead = 0;
int BuffTail = 0;
sem_t empty_sem;
sem_t full_sem;
bool initBuff()
{
if(0 != sem_init(&mpty_sem, 0, BUFF_NUM))
{
printf("empty semaphore init fail!\n");
return false;
}
if(0 != sem_init(&full_sem, 0, 0))
{
printf("full semaphore init fail!\n");
return false;
}
memset((void *)Buff, 0, BUFF_NUM * sizeof(struct BuffFormat));
BuffHead = 0;
BuffTail = 0;
return true;
}
bool getReq(char* buff, int *len)
{
if(NULL == buff || NULL == len)
{
return false;
}
sem_wait(&full_sem);
if(false == Buff[BuffHead].usedFlag)
{
return false;
}
*len = Buff[BuffHead].len;
memcpy((void*)buff, (const void*)Buff[BuffHead].Req, *len);
Buff[BuffHead].usedFlag = false;
BuffHead ++;
if(BuffHead ==BUFF_NUM)
{
BuffHead = 0;
}
sem_post(&empty_sem);
return true;
}
bool putReq(char *buff, int len)
{
if(NULL == buff || 0 == len)
{
return false;
}
sem_wait(&empty_sem);
if(true == Buff[BuffTail].usedFlag)
{
return false;
}
Buff[BuffTail].usedFlag = true;
Buff[BuffTail].len = len;
memcpy((void*)Buff[BuffTail].Req, (const void*)buff, len);
BuffTail ++;
if(BuffTail == BUFF_NUM)
{
BuffTail = 0;
}
sem_post(&full_sem);
return true;
}
//test
void* thread1(void* arg)
{
int i = 0;
char req[6] = "jwx";
while(i <= 9)
{
req[3]= '0' + i;
putReq(req, 4);
printf("put msg\n");
i ++;
}
}
//test
void* thread2(void* arg)
{
int i = 0;
char buff[LEN];
int len = 0;
while(1)
{
getReq(buff,&len);
printf("get msg:");
for(i = 0; i < len; ++i)
{
printf("%c", buff[i]);
}
printf("\n");
}
}
//test
int main()
{
pthread_t th1, th2;
if(false == initBuff())
{
return 1;
}
if(0 != pthread_create(&th1, NULL, thread1, NULL))
{
printf("create thread1 fail\n");
return 1;
}
if(0 != pthread_create(&th2, NULL, thread2, NULL))
{
printf("create thread2 fail\n");
return 1;
}
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
补充:
(1)父子关系?
先提一下父进程和子进程的概念。当用fork()创建另一个新进程时,新进程是子进程,原始进程是父进程。这创建了可能非常有用的层次关系,尤其是等待子进程终止时。例如,waitpid()函数让当前进程等待所有子进程终止。waitpid()用来在父进程中实现简单的清理过程。
而 POSIX线程就更有意思。我们一直有意避免使用“父线程”和“子线程”的说法。这是因为POSIX线程中不存在这种层次关系。虽然主线程可以创建一个新线程,新线程可以创建另一个新线程,POSIX线程标准将它们视为等同的层次。所以等待子线程退出的概念在这里没有意义
POSIX线程标准提供了有效地管理多个线程所需要的所有工具。实际上,没有父/子关系这一事实却为在程序中使用线程开辟了更创造性的方法。(2)pthread_join()和pthread_detach():
一般情况下,进程中各线程的运行相互独立的,线程的终止并不会通知,不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释 放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()的调用者将挂起并等待th1线程终止。
如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的 内存资源,同时也无法由pthread_join()同步。
例如,如果有一个线程称为线程1,线程 1 创建了称为线程 2 的线程,则线程 1 自己没有必要调用pthread_join() 来合并线程2,程序中其它任一线程都可以做到。当编写大量使用线程的代码时,这就可能允许发生有趣的事情。例如,可以创建一个包含所有已停止线程的全局“死线程列表”,然后让一个专门的清理线程专等停止的线程加到列表中。这个清理线程调用pthread_join()将刚停止的线程与自己合并。现在,仅用一个线程就巧妙和有效地处理了全部清理。
引用:http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/#toggle
(3)pthread_exit 和 return:
在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行.