。。。第十章信号的笔记还没写好,估计还是我太菜,书上的很多demo运行效果和书本上的完全不同,加上信号这一章的却是有点难的啦,等我把书上代码的坑填上后再把第十章笔记补上。
•1 线程
典型的unix进程可以看成只有一个控制线程:一个进程在一个时刻只能做一件事,有了多个线程之后,在程序设计时就可以把进程设计成在某一时刻做不止一件事,每个线程都有各自独立的任务。采用线程有很多的好处:
(1):通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。每个线程在处理时可以采用同步编程,模式,同步编程模式比异步编程模式要简单的多。
(2):每个进程使用操作系统提供的复杂机制才能实现内存和文件描述符的共享。
(3):有些问题可以分解从而提高整个程序的吞吐量。在只有一个控制线程的情况下,一个单线程进程要完成多个任务,只需要把这些任务串行化。但有多个线程时,相互独立的任务处理就可以交叉进行,此时只需要为每个任务分配一个单独的线程。
(4):交互的程序同样可以通过使用多线程来改善响应时间。
每个线程都包含有表示执行环境所必需的信息。每个进程都有一个进程ID,每个线程也有一个线程ID,进程ID
• 2 线程标识
在整个系统中是唯一的,但线程ID不同,线程ID只有在它所处的进程上下文中才有意义。
线程ID是用pthread_t数据类型来表示的,实现的时候可以用一个结构来代表pthread_t的数据类型,所以可移植的操作系统实现不能把它当做整数处理,因此必须使用一个函数来对两个线程ID进行比较。
#include<pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2);
线程可以通过调用pthread_self来获得自身的线程ID。
• 3 线程创建
#include"apue.h"
#include<pthread.h>
pthread_t ntid;
void printids(const char* s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %lu tid %lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,(unsigned long)tid);
}
void* thr_fn(void* arg)
{
printids("new thread:");
return ((void*)0);
}
int main()
{
int err;
err = pthread_create(&ntid,NULL,thr_fn,NULL);
if(err != 0)
{
err_exit(err,"can't create thread");
}
printids("main thread:");
sleep(1);
return 0;
}
• 4 线程终止
单个线程有三种方式退出,在不终止整个进程的情况下,停止它的控制流。
(1)线程可以简单的从启动例程返回,返回值是线程的退出码
(2)线程可以被同一进程中的其他线程取消
(3)线程调用pthread_exit
在线程内部调用pthread_exit退出线程,进程中的其他线程也可以调用pthread_join将线程置于分离状态。
#include"apue.h"
#include<pthread.h>
void* thr_fn1(void* arg)
{
printf("thread 1 returning\n");
return ((void*)1);
}
void* thr_fn2(void* arg)
{
printf("thread 2 exiting\n");
pthread_exit((void*)2);
}
int main()
{
int err;
pthread_t tid1,tid2;
void* tret;
err = pthread_create(&tid1,NULL,thr_fn1,NULL);
if(err != 0)
err_exit(err,"can't create thread 1");
err = pthread_create(&tid2,NULL,thr_fn2,NULL);
if(err != 0)
err_exit(err,"can't create thread 2");
err = pthread_join(tid1,&tret);
if(err != 0)
err_exit(err,"can't join with thread 1");
printf("thread 1 exit code %ld\n",(long)tret);
err = pthread_join(tid2,&tret);
if(err != 0)
err_exit(err,"can't join with thread 2");
printf("thread 1 exit code %ld\n",(long)tret);
exit(0);
}
#include"apue.h"
#include<pthread.h>
struct foo{
int a,b,c,d;
};
void printfoo(const char* s,const struct foo *fp)
{
printf("%s",s);
printf(" structure at 0x%lx\n",(unsigned long)fp);
printf(" foo.a = %d\n",fp->a);
printf(" foo.b = %d\n",fp->b);
printf(" foo.c = %d\n",fp->c);
printf(" foo.d = %d\n",fp->d);
}
void* thr_fn1(void* arg)
{
struct foo foo = {1,2,3,4};
printfoo("thread 1:\n",&foo);
pthread_exit((void*)&foo);
}
void* thr_fn2(void* arg)
{
printf("thread 2: ID is %lu\n",(unsigned long)pthread_self());
pthread_exit((void*)0);
}
int main()
{
int err;
pthread_t tid1,tid2;
struct foo *fp;
err = pthread_create(&tid1,NULL,thr_fn1,NULL);
if(err != 0)
err_exit(err,"can't create thread 1");
err = pthread_join(tid1,(void**)&fp);
if(err != 0)
err_exit(err,"can't join with thread 1");
sleep(1);
printf("parent starting second thread\n");
err = pthread_create(&tid2,NULL,thr_fn2,NULL);
if(err != 0)
err_exit(err,"can't create thread 2");
err = pthread_join(tid2,(void**)&fp);
sleep(1);
printfoo("parent:\n",fp);
exit(0);
}
从上面程序的结果可以看到,在Linux操作系统下,父进程试图去访问第一个线程传给它的结构时,由于这是线程已经结束,内存不在有效,得到了段错误的信号。
• 5 线程处理函数pthread_cleanup_push / pthread_cleanup_pop
线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序,线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。当线程是正常的退出的话,栈中的清理函数是不会被执行的。
#include"apue.h"
#include<pthread.h>
void cleanup(void* arg)
{
printf("cleanup:%s\n",(char*)arg);
}
void* thr_fn1(void* arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup,(void*)("thread 1 first handler"));
pthread_cleanup_push(cleanup,(void*)("thread 1 second handler"));
printf("thread 1 push complete\n");
if(arg)
return ((void*)1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return ((void*)1);
}
void* thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup,(void*)("thread 2 first handler"));
pthread_cleanup_push(cleanup,(void*)("thread 2 second handler"));
printf("thread 2 push complete\n");
if(arg)
pthread_exit((void*)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)2);
}
int main(void)
{
int err;
pthread_t tid1,tid2;
void *tret;
err = pthread_create(&tid1,NULL,thr_fn1,(void*)1);
if(err != 0)
err_exit(err,"can't create thread 1");
err = pthread_create(&tid2,NULL,thr_fn2,(void*)1);
if(err != 0)
err_exit(err,"can't create thread 2");
err = pthread_join(tid1,&tret);
if(err != 0)
err_exit(err,"can't join with thread 2");
printf("thread 1 exit code %ld\n",(long)tret);
err = pthread_join(tid2,&tret);
if(err != 0)
err_exit(err,"can't join with thread 2");
printf("thread 2 exit code %ld\n",(long)tret);
exit(0);
}
• 6 线程同步
当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,当多个线程可以对同一块内存进行读写操作时,需要对这些线程进行同步。
• 7 互斥量
使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。
互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放互斥量(解锁)。
对互斥量进行加锁后,其他任何线程试图对互斥量进行再次加锁都会被阻塞,直到当前线程释放该互斥量。
如果释放互斥量时,存在多个线程在阻塞等待,那么所有该锁上的阻塞线程都会变成可运行状态。第一个变为运行状态的线程就可以对互斥量进行加锁,其他线程就会看到互斥量依然是锁着的,然后重新阻塞等待。
demo:
#include <stdlib.h>
#include <pthread.h>
struct foo{
int f_count;
pthread_mutex_t f_lock;
int f_id;
/*...more stuff here... */
};
struct foo *foo_alloc(int id) /* 申请并分配对象空间 */
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo)))!= NULL){
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL)!= 0){
free(fp);
return (NULL);
}
/* ... continue initialization ...*/
}
return (fp);
}
void foo_hold(struct foo *fp) /* 对对象增加了一个引用 */
{
pthread_mutex_lock(&fp->f_lock); //因为可能有多个线程创建并持有对象,所以需要加互斥量
fp->f_count++; /* 引用计数加1 */
pthread_mutex_unlock(&fp->f_lock);
}
void foo_rele(struct foo *fp)/* 释放引用对象 */
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0){ /* last reference */
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}else{
pthread_mutex_unlock(&fp->f_lock);
}
}
• 8 避免死锁
如果一个线程试图对同一个互斥量加两次锁,那么就会陷入死锁状态,在使用互斥量时,有很多不明显的地方也会产生死锁。
可以通过仔细控制互斥量加锁的顺序来避免死锁的发生。或者使用pthread_mutex_trylock、pthread_mutex_timedlock来实现。
下面的例子展示了两种加锁的方法,在第一个例子中,在同时需要两个互斥量时,总是让它们以相同的顺序加锁,这样可以避免死锁。第二个互斥量维护着一个用于跟踪foo数据结构的散列列表。这样hashlock互斥量既可以保护foo数据结构中的散列表fh,又可以保护散列链字段f_next。foo结构中的f_lock互斥量保护对foo结构中的其他字段的访问。第二个例子则使用hashlock互斥量对整个hash表加锁更改来操作引用计数。
demo1:
#include <stdlib.h>
#include <pthread.h>
#define NHASH 29
#define HASH(id) (((unsigned long)id)%NHASH)
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo {
int f_count;
pthread_mutex_t f_lock;
int f_id;
struct foo *f_next; /* protected by hashlock */
/* ... more stuff here ... */
};
struct foo *
foo_alloc(int id) /* allocate the object */
{
struct foo *fp;
int idx;
if ((fp = malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
return(NULL);
}
idx = HASH(id);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp;
pthread_mutex_lock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
/* ... continue initialization ... */
pthread_mutex_unlock(&fp->f_lock);
}
return(fp);
}
void
foo_hold(struct foo *fp) /* add a reference to the object */
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
struct foo *
foo_find(int id) /* find an existing object */
{
struct foo *fp;
pthread_mutex_lock(&hashlock);
for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) {
if (fp->f_id == id) {
foo_hold(fp);
break;
}
}
pthread_mutex_unlock(&hashlock);
return(fp);
}
void
foo_rele(struct foo *fp) /* release a reference to the object */
{
struct foo *tfp;
int idx;
pthread_mutex_lock(&fp->f_lock);
if (fp->f_count == 1) { /* last reference */
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_lock(&hashlock);
pthread_mutex_lock(&fp->f_lock);
/* need to recheck the condition */
if (fp->f_count != 1) {
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
return;
}
/* remove from list */
idx = HASH(fp->f_id);
tfp = fh[idx];
if (tfp == fp) {
fh[idx] = fp->f_next;
} else {
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&hashlock);
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
}
}
demo2:
#include <stdlib.h>
#include <pthread.h>
#define NHASH 29
#define HASH(id) (((unsigned long)id)%NHASH)
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo {
int f_count; /* protected by hashlock */
pthread_mutex_t f_lock;
int f_id;
struct foo *f_next; /* protected by hashlock */
/* ... more stuff here ... */
};
struct foo *
foo_alloc(int id) /* allocate the object */
{
struct foo *fp;
int idx;
if ((fp = malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
return(NULL);
}
idx = HASH(id);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp;
pthread_mutex_lock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
/* ... continue initialization ... */
pthread_mutex_unlock(&fp->f_lock);
}
return(fp);
}
void
foo_hold(struct foo *fp) /* add a reference to the object */
{
pthread_mutex_lock(&hashlock);
fp->f_count++;
pthread_mutex_unlock(&hashlock);
}
struct foo *
foo_find(int id) /* find an existing object */
{
struct foo *fp;
pthread_mutex_lock(&hashlock);
for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) {
if (fp->f_id == id) {
fp->f_count++;
break;
}
}
pthread_mutex_unlock(&hashlock);
return(fp);
}
void
foo_rele(struct foo *fp) /* release a reference to the object */
{
struct foo *tfp;
int idx;
pthread_mutex_lock(&hashlock);
if (--fp->f_count == 0) { /* last reference, remove from list */
idx = HASH(fp->f_id);
tfp = fh[idx];
if (tfp == fp) {
fh[idx] = fp->f_next;
} else {
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&hashlock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&hashlock);
}
}
下面这个例子演示的是pthread_mutex_timedlock的使用
#include"apue.h"
#include<pthread.h>
int main(void)
{
int err;
struct timespec tout;
struct tm* tmp;
char buf[64];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
printf("mutex is locked\n");
clock_gettime(CLOCK_REALTIME,&tout);
tmp = localtime(&tout.tv_sec);
strftime(buf,sizeof(buf),"%r",tmp);
printf("current time is %s\n",buf);
tout.tv_sec += 10;
err = pthread_mutex_timedlock(&lock,&tout);
clock_gettime(CLOCK_REALTIME,&tout);
tmp = localtime(&tout.tv_sec);
strftime(buf,sizeof (buf),"%r",tmp);
printf("the time is now%s\n",buf);
if(err == 0)
printf("mutex locked again!\n");
else {
printf("can't lock mvtex again:%s\n");
}
exit(0);
}