int pthread_create(pthread_t *newthread,const pthread_attr_t *attr,
void *(*start_rtn)(void*),void *arg);
Create a new thread, starting with execution of START-ROUTINE getting passed ARG. Creation attributed come from ATTR. The new
handle is stored in *NEWTHREAD.
第一个参数表示新建线程的句柄,表示线程ID,第二个参数表示线程属性,第三个参数表示线程执行的函数,第四个参数表示函数执行的参数。
void pthread_exit( void * retval );
Terminate calling thread.
用于终止调用它的线程,并返回一个指向retval的指针。
int pthread_join (pthread_t th, void ** thread_return);
Make calling thread wait for termination of the thread TH. The
exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN is not NULL.
阻塞当前线程,等待th线程的结束。如果thread_return的值不是NULL,那么将会储存pthread_exit的返回值。如果线程结束,该函数会立即返回,被阻塞的函数的资源被回收。
int pthread_attr_init (pthread_attr_t * attr)
Initialize thread attribute *ATTR with default attributes (detachstate is PTHREAD_JOINABLE, scheduling policy is SCHED_OTHER,no user-provided stack)
该函数用于初始化线程属性attr,分离状态初始化为PTHREAD_JOINABLE,调度策略是SCHED_OTHER,没有用户提供的堆栈。线程的detachstate分为PTHREAD_CREATE_JOINABLE,PTHREAD_CREATE_DETACHED。所谓线程的分离状态,表示的意思是线程以什么样的方式结束自己,线程属于分离状态时,不需要被其他线程等待,调用pthread_join也不会阻塞当前线程,主线程和子线程相当于两个序列,各自执行,自己运行结束以后,线程就终止了,释放掉自己的资源。而非分离状态的线程,原有主线程会等待创建线程的执行,等待pthread_join函数返回后,才终止线程,释放系统资源。
int pthread_attr_setdetachstate (pthread_attr_t * attr, int detachstate)
Set detach state attribute.
用于设置函数的分离状态,detachstate被设置为PTHREAD_CREATE_JOINABLE表示非分离, PTHREAD_CREATE_DETACHED表示分离。
int pthread_attr_destroy (pthread_attr_t * attr)
用于删除线程属性。
在这里先介绍这几个常用的,还有一些函数目前还没有在实践中用过,后续有机会会继续补充。下面是一个关于线程操作的实例:
#include
#include
#include
int mCount = 6;
void* createThread(void* arg);
void* createThread(void* arg){
int count = (int)(*((int*) arg));
for (int i = 0; i < count; i++)
{
printf("createThread i = %d.\n",i);
if (i == 3)
{
pthread_exit("Terminal thread.\n");
}
}
}
void main(){
pthread_t tid1, tid2;
pthread_attr_t pthread_attr1, pthread_attr2;
void* retval1 = NULL;
void* retval2 = NULL;
pthread_attr_init(&pthread_attr1);
pthread_attr_init(&pthread_attr2);
pthread_attr_setdetachstate(&pthread_attr1, PTHREAD_CREATE_JOINABLE);
pthread_attr_setdetachstate(&pthread_attr2, PTHREAD_CREATE_DETACHED);
pthread_create(&tid1, &pthread_attr1, createThread, &mCount);
pthread_create(&tid2, &pthread_attr2, createThread, &mCount);
pthread_join(tid1, &retval1);
pthread_join(tid2, &retval2);
printf("Thread 1 return : %s\n", (char*)retval1);
printf("Thread 2 return : %s\n", (char*)retval2);
pthread_attr_destroy(&pthread_attr1);
pthread_attr_destroy(&pthread_attr2);
}
多线程同步的方法主要有四种,互斥锁、条件变量、信号量、读写锁。目前读写锁还没有认真研究过,所以这里先只介绍前三种。
多线程运行的过程中,会涉及用到一些共享资源,这些资源被成为临界资源,每个线程访问临界资源的那部分代码叫做临界区,保证多线程访问临界资源不产生冲突,实现多线程之间的互斥与同步用到的机制就是锁机制。
互斥锁是通过加锁的方式来控制对临界资源的访问,在访问临界资源之前,进行加锁操作,访问完临界资源之后,进行解锁。在加锁之后,任何想对临界资源进行操作的线程都会被挂起(不占用CPU资源),直到加锁线程访问完临界资源将锁释放后,之前挂起的线程才会被唤醒,同时继续进行加锁,并执行后续操作。
在使用锁之前,首先要进行变量初始化:
int pthread_mutex_init (pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr)
通常来说,第二个参数填写NULL就可以了。
还有一种静态初始化的方式:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
互斥量加锁和解锁的函数如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
锁在使用最后,需要释放掉。
int pthread_mutex_destroy (pthread_mutex_t *__mutex)
下面看一个简单的例子,我们建立两个线程,分别做一个简单的自加运算,然后打印运算后的值,先看一下不加Mutex的代码和结果:
#include
#include
#include
#include
#include
static int iCount = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void func(void);
void func(void){
for (int i = 0; i < 3; i++)
{
printf("before add:iCount = %d.\n", iCount);
iCount++;
printf("after add: iCount = %d.\n", iCount);
}
}
void main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, (void *)func, NULL);
pthread_create(&tid2, NULL, (void *)func, NULL);
pthread_join(tid1, NULL);
pthread_join(tid1, NULL);
pthread_mutex_destroy(&mutex);
}
加锁之后的代码和结果如下:
#include
#include
#include
#include
#include
static int iCount = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void func(void);
void func(void){
for (int i = 0; i < 3; i++)
{
pthread_mutex_lock(&mutex);
printf("before add:iCount = %d.\n", iCount);
iCount++;
printf("after add: iCount = %d.\n", iCount);
pthread_mutex_unlock(&mutex);
}
}
void main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, (void *)func, NULL);
pthread_create(&tid2, NULL, (void *)func, NULL);
pthread_join(tid1, NULL);
pthread_join(tid1, NULL);
pthread_mutex_destroy(&mutex);
}
可以看到,加锁之后,两个线程打印出来的值不会像前面那样乱序了。
条件变量是通过线程间共享的全局变量来进行线程同步的一种机制:一个线程在满足某个条件后,阻塞该线程,将线程挂起,等待“条件变量”(pthread_cond_t)的变化,直到另一个线程满足另一个条件后,发信号给“条件变量”,被阻塞的线程被重新唤醒。条件变量通常搭配互斥锁共同使用,在改变条件之前,必须先执行加锁操作。
/* Initialize condition variable COND using attributes ATTR, or use
the default values if later is NULL. */
int pthread_cond_init (pthread_cond_t * cond,
const pthread_condattr_t * cond_attr);
根据条件变量属性attr来初始化条件变量pthread_cond_t,属性attr默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED 则为多个进程间各线程公用。
/* Destroy condition variable COND. */
int pthread_cond_destroy (pthread_cond_t *__cond);
/* Wake up one thread waiting for condition variable COND. */
int pthread_cond_signal (pthread_cond_t * cond);
唤醒一个正处于阻塞状态的线程,使得阻塞的线程继续执行。pthread_cond_signal可能会存在的一个问题就是信号丢失,意思就是,如果pthread_cond_signal发生在pthread_cond_wait之前,当前没有线程阻塞,条件也会被复位,等到下一个pthread_cond_wait进入阻塞状态的时候,将不会受到任何影响,造成唤醒丢失现象,使用信号量将会避免该问题发生,后面会进行详细介绍。
/*Wake up all threads waiting for condition variables COND. */
int pthread_cond_broadcast (pthread_cond_t * cond);
唤醒所有正处于阻塞状态的线程,使得阻塞的线程继续执行。
/*Wait for condition variable COND to be signaled or broadcast.
MUTEX is assumed to be locked before. */
int pthread_cond_wait (pthread_cond_t * cond,
pthread_mutex_t * mutex);
阻塞当前线程,等待其他线程发出信号后,再唤醒当前线程。需要注意的一点就是,pthread_cond_wait要放在while循环内,而不要放在If判断内,否则产生虚假唤醒的时候,函数继续往下执行可能会产生错误。
while(条件不满足)
{
pthread_cond_wait(cond, mutex);
}
// 而不是:
If( 条件不满足 )
{
pthread_cond_wait(cond,mutex);
}
下面看一个关于条件变量应用的实例,我们建立了两个线程,一个生产者线程,一个消费者线程,共享资源是一个链表结构的产品,具体代码如下:
#include
#include
#include
#include
#include
typedef struct product{
int num;
struct product *next;
}stProduct;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
stProduct *pstHead = NULL;
stProduct *pstProduct = NULL;
stProduct *pstTail = NULL;
void producer();
void customer();
void producer()
{
srand(time(NULL));
for (int i = 0; i < 4; i++)
{
pthread_mutex_lock(&mutex);
pstProduct = (stProduct *)malloc(sizeof(stProduct));
pstProduct->num = rand()%100;
pstProduct->next = NULL;
if(pstHead == NULL){
pstHead = pstProduct;
pstTail = pstHead;
}else{
pstTail->next = pstProduct;
pstTail = pstProduct;
}
printf("[%d] product is ready.\n", pstTail->num);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(3);
}
}
void customer()
{
for (int i = 0; i < 4; i++)
{
pthread_mutex_lock(&mutex);
while (pstHead == NULL)
{
printf("head is NULL. \n");
pthread_cond_wait(&cond, &mutex);
}
printf("[%d] product is saled.\n", pstHead->num);
pstHead = pstHead->next;
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t tid_producer, tid_customer;
pthread_create(&tid_customer, NULL, (void *)&producer, NULL);
pthread_create(&tid_producer, NULL, (void *)&customer, NULL);
pthread_join(tid_customer, NULL);
pthread_join(tid_producer, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
上述例子当中,并没有产生信号丢失现象,为了和下面即将要讲述的信号量做个对比,这里再给大家看一个使用条件变量发生信号丢失的例子,代码如下。
#include
#include
#include
#include
#include
void signalWait();
void signalSend();
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void signalWait()
{
pthread_mutex_lock(&mutex);
printf("wait signal.\n");
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("signal is received.\n");
}
void signalSend()
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
printf("send signal.\n");
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t tid_send, tid_wait;
pthread_create(&tid_send, NULL, (void *)&signalSend, NULL);
pthread_create(&tid_wait, NULL, (void *)&signalWait, NULL);
pthread_join(tid_send, NULL);
pthread_join(tid_wait, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
从上图运行结果可以看出,如果pthread_cond_signal发生在wait之前,信号将会被提前释放掉,等到pthread_cond_wait被调用时,线程将会一直进入阻塞状态,从而不会被唤醒。下面将上述代码进行一下修改,在wait之后再调用一次pthread_cond_signal,来看一下结果。
#include
#include
#include
#include
#include
void signalWait();
void signalSend();
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void signalWait()
{
pthread_mutex_lock(&mutex);
printf("wait signal.\n");
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("signal is received.\n");
}
void signalSend()
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
printf("send signal.\n");
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t tid_send, tid_wait, tid_resend;
pthread_create(&tid_send, NULL, (void *)&signalSend, NULL);
pthread_create(&tid_wait, NULL, (void *)&signalWait, NULL);
pthread_create(&tid_resend, NULL, (void *)&signalSend, NULL);
pthread_join(tid_send, NULL);
pthread_join(tid_wait, NULL);
pthread_join(tid_resend, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
可以看到,只有pthread_cond_signal调用在phread_cond_wait之后,才能有效将阻塞的线程唤醒。
对于信号量,可以理解为一个非负的整数计数器,信号量为正数时,表示有可用的资源,信号量为0时,表示不可访问临界资源,线程将被阻塞。
/* Initialize semaphore object SEM to VALUE. If PSHARED then share it
with other processes. */
int sem_init (sem_t * sem, int pshared, unsigned int value)
用value初始化信号量的值,如果pshared为0,表示只能在当前进程中各个线程进行共享,如果pshared为1,则表示可以在进程中共享。
/* Free resources associated with semaphore object SEM. */
int sem_destroy (sem_t * sem)
/* Wait for SEM being posted.*/
int sem_wait (sem_t * sem);
信号量的值减一,当信号量减为0时,当前线程阻塞。
/* Test whether SEM is posted. */
int sem_trywait (sem_t * sem) ;
如果当前信号量为0,不会阻塞当前线程,而会返回EAGAIN。
/* Post SEM. */
int sem_post (sem_t * sem)
信号量的值加一。
/* Get current value of SEM and store it in *SVAL. */
int sem_getvalue (sem_t * sem, int * sval)
获取当前信号量的值,存储到sval中。
与条件变量不同,即使sem_post发生在sem_wait之前,也不会发生信号丢失现象,因为调用sem_post之后,信号量sem的值加一,直到下次调用sem_wait之前sem的值都不会自动复位。
下面是关于信号量使用的一个demo,简单模拟一个的顾客打车的情况,每来一位顾客,信号量减一,如果信号量为零,线程将会挂起,每来一辆车,信号量值加一。
#include
#include
#include
#include
#include
sem_t sem;
static int driverNum = 3;
static int customerNum = 4;
void driverFunc(void);
void customerFunc(void);
void driverFunc(void)
{
for (int i = 0; i < driverNum; i++)
{
sleep(3);
sem_post(&sem);
printf("driver [%d] comes.\n", i);
}
}
void customerFunc(void)
{
for (int i = 0; i < customerNum; i++)
{
sleep(1);
printf("customer [%d] comes.\n", i);
sem_wait(&sem);
printf("customer [%d] got car.\n", i);
}
}
void main()
{
pthread_t customer, driver;
//sem init
sem_init(&sem, 1, 0);
//thread create
pthread_create(&driver, NULL, (void *)&driverFunc, NULL);
pthread_create(&customer, NULL, (void *)&customerFunc, NULL);
//thread join
pthread_join(customer, NULL);
pthread_join(driver, NULL);
//sem destory
sem_destroy(&sem);
}
运行结果如下:
由于有3个顾客到来,但是只有2辆车,所以第3个顾客达到后,线程将会被挂起。
下面来验证一下,前文所提到的,信号量不会发生信号丢失现象。把driverFunc内部sleep时间修改为1秒,把customerFunc内部sleep时间修改为3秒。再来看一下运行结果:
即使司机先来到,也不会发生信号丢失现象,顾客一来的时候,会立刻打到车。
最后这部分,为了防止大家混淆信号量、条件变量和互斥锁,能更好的记住这三者应该分别在什么情况下使用,对于这三者之间的联系与区别,我做了一些简单的总结。