进程或线程间的通信,通常是为了实现各进程或线程之间的同步,我们的讨论主要分为以下几个方面:
1.消息传递(管道(匿名管道),FIFO(有名管道),消息队列)
2.同步(互斥锁,读写锁,条件变量,信号量)
3.共享内存区
本节我们集中讨论互斥锁,条件变量来实现进程跟线程之间的同步。
线程间的同步:互斥锁实现互斥访问临界资源
初始化和反初始化:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态分配的互斥锁的初始化;互斥锁用pthread_mutex_t类型表示
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);//用互斥锁属性初始化,默认属性为NULL
int pthread_mutex_destroy(pthread_mutex_t *mutex);
上锁与解锁:
int pthread_mutex_lock(pthread_mutex_t *mutex); //上锁,如果未获得锁,则阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); //上锁,如果未获得锁,不阻塞,直接返回EBUSY
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *mutex,const struct timespec *abs_timeout); //如果未获得锁,则阻塞到abs_timeout后,返回超时错误,ETIMEDOUT,若在abs_timeout时刻之前获得了锁,则成功返回,加锁成功。
/* include main */
#include "unpipc.h"
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
int nitems; /* read-only by producer and consumer */
struct {
pthread_mutex_t mutex;
int buff[MAXNITEMS];
int nput;
int nval;
} shared = { PTHREAD_MUTEX_INITIALIZER };
void *produce(void *), *consume(void *);
int
main(int argc, char **argv)
{
int i, nthreads, count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS], tid_consume;
if (argc != 3)
err_quit("usage: prodcons2 <#items> <#threads>");
nitems = min(atoi(argv[1]), MAXNITEMS);
nthreads = min(atoi(argv[2]), MAXNTHREADS);
Set_concurrency(nthreads);
/* start all the producer threads */
for (i = 0; i < nthreads; i++) {
count[i] = 0;
Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
}
/* wait for all the producer threads */
for (i = 0; i < nthreads; i++) {
Pthread_join(tid_produce[i], NULL);
printf("count[%d] = %d\n", i, count[i]);
}
/* start, then wait for the consumer thread */
Pthread_create(&tid_consume, NULL, consume, NULL);
Pthread_join(tid_consume, NULL);
exit(0);
}
/* end main */
/* include producer */
void *
produce(void *arg)
{
for ( ; ; ) {
Pthread_mutex_lock(&shared.mutex);
if (shared.nput >= nitems) {
Pthread_mutex_unlock(&shared.mutex);
return(NULL); /* array is full, we're done */
}
shared.buff[shared.nput] = shared.nval;
shared.nput++;
shared.nval++;
Pthread_mutex_unlock(&shared.mutex);
*((int *) arg) += 1;
}
}
void *
consume(void *arg)
{
int i;
for (i = 0; i < nitems; i++) {
if (shared.buff[i] != i)
printf("buff[%d] = %d\n", i, shared.buff[i]);
}
return(NULL);
}
/* end producer */
上面是使用互斥锁实现的多个生产者,一个消费者的例子,生产者往buff中填入数据,消费者取数据比较填入数据是否正确。为了简化逻辑,我们让生产者先执行,他们互斥的访问公共内存buff,以及数组下标nput.当生产者生产完数据后,我们再执行消费者线程。我们也可以同时执行生产者跟消费者线程,那样的话就需要一种机制来同步生产者跟消费者线程,这就是即将介绍的条件变量。
线程间的同步:条件变量实现线程之间的同步
int pthread_cond_destroy(pthread_cond_t *cond); //反初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //用属性初始化,默认属性为NULL
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态条件变量初始化
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);//线程阻塞一定的时间
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//线程阻塞,直到其他线程调用cond_signal唤醒之
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒cond_wait阻塞的所有线程线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒cond_wait阻塞的一个线程
总的来说,条件变量进行线程同步的代码如下:
struct{
pthread_mutex_t mutex;
pthread_cond_t cond;
/*其他维护本条件的各个变量*/
}var={PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,/*其他维护本条件的各个变量的初始化*/};
//在线程1中,条件发生,设置条件为真
pthread_mutex_lock(&var.mutex);
设置条件为真
pthread_cond_signal(&var.cond);
pthread_mutex_unlock(&var.mutex);
//在线程2中,线程阻塞,在while循环中等待条件的发生
pthread_mutex_lock(&var.mutex);
while(条件为假)
pthread_cond_wait(&var.cond,&var.mutex);
修改条件
pthread_mutex_unlock(&var.mutex);
现在我们用条件变量修改生产者-消费者程序的代码,这一次让消费者跟生产者一起运行,并用条件变量来进行同步:
#include "unpipc.h"
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
/* globals shared by threads */
int nitems; /* read-only by producer and consumer */
int buff[MAXNITEMS];
struct {
pthread_mutex_t mutex;
int nput; /* next index to store */
int nval; /* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };
struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int nready; /* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
/* end globals */
void *produce(void *), *consume(void *);
/* include main */
int main(int argc, char **argv)
{
int i, nthreads, count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS], tid_consume;
if (argc != 3)
err_quit("usage: prodcons6 <#items> <#threads>");
nitems = min(atoi(argv[1]), MAXNITEMS);
nthreads = min(atoi(argv[2]), MAXNTHREADS);
Set_concurrency(nthreads + 1);
/* 4create all producers and one consumer */
for (i = 0; i < nthreads; i++) {
count[i] = 0;
Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
}
Pthread_create(&tid_consume, NULL, consume, NULL);
/* wait for all producers and the consumer */
for (i = 0; i < nthreads; i++) {
Pthread_join(tid_produce[i], NULL);
printf("count[%d] = %d\n", i, count[i]);
}
Pthread_join(tid_consume, NULL);
exit(0);
}
/* end main */
/* include prodcons */
void *produce(void *arg)
{
for ( ; ; ) {
Pthread_mutex_lock(&put.mutex);
if (put.nput >= nitems) {
Pthread_mutex_unlock(&put.mutex);
return(NULL); /* array is full, we're done */
}
buff[put.nput] = put.nval;
put.nput++;
put.nval++;
Pthread_mutex_unlock(&put.mutex);
Pthread_mutex_lock(&nready.mutex);
if (nready.nready == 0)
Pthread_cond_signal(&nready.cond);
nready.nready++;
Pthread_mutex_unlock(&nready.mutex);
*((int *) arg) += 1;
}
}
void *consume(void *arg)
{
int i;
for (i = 0; i < nitems; i++) {
Pthread_mutex_lock(&nready.mutex);
while (nready.nready == 0)
Pthread_cond_wait(&nready.cond, &nready.mutex);
nready.nready--;
Pthread_mutex_unlock(&nready.mutex);
if (buff[i] != i)
printf("buff[%d] = %d\n", i, buff[i]);
}
return(NULL);
}
/* end prodcons */
在上述程序中,我们用nready变量跟踪生产者已生产的数目,当生产者数目为0,表示buff数据为空,消费者调用pthread_cond_wait阻塞,当buff数据从0变为1时,表示已经生产了数据,生产者调用pthread_cond_sinnal唤醒消费者。
在本节的最后,我们给一个典型的tcp服务器程序的实现demo,我们的服务器程序在程序启动阶段就创建一个线程池,之后只让主线程调用accept,并把accept后的套接字描述符传给线程池中的某个线程,让该线程进行处理连接。
/* include serv08 */
#include "unpthread.h"
typedef struct {
pthread_t thread_tid; /* thread ID */
long thread_count; /* # connections handled */
} Thread;
Thread *tptr; /* array of Thread structures; calloc'ed */
#define MAXNCLI 32
int clifd[MAXNCLI], iget, iput;
static int nthreads;
pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;
//创建线程
void thread_make(int i)
{
void *thread_main(void *);
Pthread_create(&tptr[i].thread_tid, NULL, &thread_main, (void *) i);
return; /* main thread returns */
}
//线程函数,处理连接
void *thread_main(void *arg)
{
int connfd;
void web_child(int);
printf("thread %d starting\n", (int) arg);
for ( ; ; ) {
Pthread_mutex_lock(&clifd_mutex);
while (iget == iput)
Pthread_cond_wait(&clifd_cond, &clifd_mutex);
connfd = clifd[iget]; /* connected socket to service */
if (++iget == MAXNCLI)
iget = 0;
Pthread_mutex_unlock(&clifd_mutex);
tptr[(int) arg].thread_count++;
web_child(connfd); /* process request */
Close(connfd);
}
}
int main(int argc, char **argv)
{
int i, listenfd, connfd;
void sig_int(int), thread_make(int);
socklen_t addrlen, clilen;
struct sockaddr *cliaddr;
if (argc == 3)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
else if (argc == 4)
listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
else
err_quit("usage: serv08 [ <host> ] <port#> <#threads>");
cliaddr = Malloc(addrlen);
nthreads = atoi(argv[argc-1]);
tptr = Calloc(nthreads, sizeof(Thread));
iget = iput = 0;
/* 4create all the threads */
for (i = 0; i < nthreads; i++)
thread_make(i); /* only main thread returns */
Signal(SIGINT, sig_int);
for ( ; ; ) {
clilen = addrlen;
connfd = Accept(listenfd, cliaddr, &clilen);
Pthread_mutex_lock(&clifd_mutex);
clifd[iput] = connfd;
if (++iput == MAXNCLI)
iput = 0;
if (iput == iget)
err_quit("iput = iget = %d", iput);
Pthread_cond_signal(&clifd_cond);
Pthread_mutex_unlock(&clifd_mutex);
}
}
/* end serv08 */
void sig_int(int signo)
{
int i;
void pr_cpu_time(void);
pr_cpu_time();
for (i = 0; i < nthreads; i++)
printf("thread %d, %ld connections\n", i, tptr[i].thread_count);
exit(0);
}