20145240《信息安全系统设计基础》第十三周学习总结
20145240《信息安全系统设计基础》第十三周学习总结
教材学习内容总结
第十二章 并发编程
1、并发:逻辑控制流在时间上重叠
2、并发程序:使用应用级并发的应用程序称为并发程序
3、三种基本的构造并发程序的方法:
(1)进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。
(2)I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
(3)线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。
12.1 基于进程的并发编程
1、构造并发服务器的自然方法就是,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
2、因为父子进程中的已连接描述符都指向同一个文件表表项,所以父进程关闭它的已连接描述符的拷贝是至关重要的,而且由此引起的存储器泄露将最终消耗尽可用的存储器,使系统崩溃。
12.1.1 基于进程的并发服务器
基于进程的并发echo服务器的重点内容
(1)需要一个SIGCHLD处理程序,来回收僵死子进程的资源。
(2)父子进程必须关闭各自的connfd拷贝。对父进程尤为重要,以避免存储器泄露。
(3)套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
12.1.2 关于进程的优劣
注意:进程的模型:共享文件表,但不是共享用户地址空间。 优点:一个进程不可能不小心覆盖两一个进程的虚拟存储器。
缺点:独立的地址空间使得进程共享状态信息变得更加困难。进程控制和IPC的开销很高。
Unix IPC是指所有允许进程和同一台主机上其他进程进行通信的技术,包括管道、先进先出(FIFO)、系统V共享存储器,以及系统V信号量。
12.2 基于I/O多路复用的并发编程
1、echo服务器必须响应两个相互独立的I/O时间:
(1)网络客户端发起连接请求
(2)用户在键盘上键入命令行。
2、I/O多路复用技术的基本思路:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
3、将描述符集合看成是n位位向量:b(n-1),……b1,b0
每个位bk对应于描述符k,当期仅当bk=1,描述符k才表明是描述符集合的一个元素。可以做以下三件事:
(1)分配它们;
(2)将一个此种类型的变量赋值给另一个变量;
(3)用FDZERO、FDSET、FDCLR和FDISSET宏指令来修改和检查它们。
4、echo函数:将来自科幻段的每一行回送回去,直到客户端关闭这个链接。
12.2.1 基于I/O多路复用的并发时间驱动服务器
状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。
12.2.2 I/O多路复用技术的优势
事件驱动器的设计优点:
(1)比基于进程的设计给了程序员更多的对程序行为的控制
(2)运行在单一进程上下文中,因此,每个逻辑流都能访问该进程的全部地址空间,使得流之间共享数据变得很容易。
(3)不需要进程上下文切换来调度新的流。
缺点:
(1)编码复杂
(2)不能充分利用多核处理器
粒度:每个逻辑流每个时间片执行的指令数量。并发粒度就是读一个完整的文本行所需要的指令数量。
12.3 基于线程的并发编程
1、线程:运行子啊进程上下文中的逻辑流。
2、线程有自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。
12.3.1 线程执行模型
1、主线程:每个进程开始生命周期时都是单一线程。
对等线程:某一时刻,主线程创建的对等线程
2、线程与进程的不同:
(1)线程的上下文切换要比进程的上下文切换快得多;
(2)和一个进程相关的线程组成一个对等池,独立于其他线程创建的线程。
(3)主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。
3、对等池的影响
(1)一个线程可以杀死它的任何对等线程;
(2)等待它的任意对等线程终止;
(3)每个对等线程都能读写相同的共享资源。
12.3.2 Posix线程
线程例程:线程的代码和本地数据被封装在一个线程例程中。每一个线程例程都以一个通用指针作为输入,并返回一个通用指针。
12.3.3 创建线程
pthread create函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。新线程可以通过调用pthread _self函数来获得自己的线程ID。
12.3.4 终止线程
一个线程的终止方式:
(1)当顶层的线程例程返回时,线程会隐式的终止;
(2)通过调用pthread _exit函数,线程会显示地终止。如果主线程调用pthread _exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程。
12.3.5 回收已终止线程的资源
pthread _join函数会阻塞,直到线程tid终止,回收已终止线程占用的所有存储器资源。pthread _join函数只能等待一个指定的线程终止。
12.3.6 分离线程
1、在任何一个时间点上,线程是可结合的或者是分离的。一个可结合的线程能够被其他线程收回其资源和杀死;一个可分离的线程是不能被其他线程回收或杀死的。它的存储器资源在它终止时有系统自动释放。
2、默认情况下,线程被创建成可结合的,为了避免存储器漏洞,每个可集合的线程都应该要么被其他进程显式的回收,要么通过调用pthread _detach函数被分离。
12.3.7 初始化线程
pthread _once函数允许初始化与线程例程相关的状态。
once _control变量是一个全局或者静态变量,总是被初始化为PTHREAD _ONCE _INIT.
12.3.8 一个基于线程的并发服务器
对等线程的赋值语句和主线程的accept语句之间引入了竞争。
12.4 多线程程序中的变量共享
12.4.1 线程存储器模型
1、每个线程和其他线程一起共享进程上下文的剩余部分。包括整个用户虚拟地址空间,是由只读文本、读/写数据、堆以及所有的共享库代码和数据区域组成的。线程也共享同样的打开文件的集合。
2、任何线程都可以访问共享虚拟存储器的任意位置。寄存器是从不共享的,而虚拟存储器总是共享的。
12.4.2 将变量映射到存储器
1、全局变量:虚拟存储器的读/写区域只会包含每个全局变量的一个实例。
2、本地自动变量:定义在函数内部但没有static属性的变量。
3、本地静态变量:定义在函数内部并有static属性的变量。
12.4.3 共享变量
变量v是共享的,当且仅当它的一个实例被一个以上的线程引用。
12.5 用信号量同步线程
1、共享变量引入了同步错误的可能性。
2、线程i的循环代码分解为五部分:
Hi:在循环头部的指令块
Li:加载共享变量cnt到寄存器%eax的指令,%eax表示线程i中的寄存器%eax的值
Ui:更新(增加)%eax的指令
Si:将%eaxi的更新值存回到共享变量cnt的指令
Ti:循环尾部的指令块。
12.5.1 进度图
1、进度图将指令执行模式化为从一种状态到另一种状态的转换。转换被表示为一条从一点到相邻点的有向边。合法的转换是向右或者向上。
2、临界区:对于线程i,操作共享变量cnt内容的指令构成了一个临界区。
3、互斥的访问:确保每个线程在执行它的临界区中的指令时,拥有对共享变量的互斥的访问。
4、安全轨迹线:绕开不安全区的轨迹线
不安全轨迹线:接触到任何不安全区的轨迹线就叫做不安全轨迹线
5、任何安全轨迹线都能正确的更新共享计数器。
12.5.2 信号量
1、当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
2、信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值。
12.5.3 使用信号量来实现互斥
1、二元信号量:将每个共享变量与一个信号量s联系起来,然后用P(S)和V(s)操作将这种临界区包围起来,这种方式来保护共享变量的信号量。
2、互斥锁:以提供互斥为目的的二元信号量
加锁:一个互斥锁上执行P操作称为对互斥锁加锁,执行V操作称为对互斥锁解锁。对一个互斥锁加了锁但还没有解锁的线程称为占用了这个互斥锁。
计数信号量:一个呗用作一组可用资源的计数器的信号量
12.5.4 利用信号量来调度共享资源
1、信号量的作用:
(1)提供互斥
(2)调度对共享资源的访问
2、生产者—消费者问题:生产者产生项目并把他们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们。
3、读者—写者问题:
(1)读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
(2)写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。
(3)饥饿就是一个线程无限期地阻塞,无法进展。
12.6 使用线程提高并行性
写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序是一个运行在多个处理器上的并发程序。并行程序的集合是并发程序集合的真子集。
12.7 其他并发问题
12.7.1 线程安全
1、线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
2、线程不安全的类:
(1)不保护共享变量的函数
(2)保持跨越多个调用的状态的函数。
(3)返回指向静态变量的指针的函数。解决办法:重写函数和加锁拷贝。
(4)调用线程不安全函数的函数。
12.7.2 可重入性
1、可重入函数:当它们被多个线程调用时,不会引用任何共享数据。可重入函数是线程安全函数的一个真子集 。
2、关键思想是我们用一个调用者传递进来的指针取代了静态的next变量。
3、显式可重入:没有指针,没有引用静态或全局变量
隐式可重入:允许它们传递指针
4、可重入性即使调用者也是被调用者的属性,并不只是被调用者单独的属性。
12.7.4 竞争
1、竞争:当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点时,就会发生竞争。
2、线程化的程序必须对任何可行的轨迹线都正确工作。
12.7.5 死锁
1、死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
2、程序员使用P和V操作不当,以至于两个信号量的禁止区域重叠。
3、重叠的禁止区域引起了一组称为死锁区域的状态。
4、死锁是不可预测的。
实践
condvar
-
由于调用了pthread,gcc编译的时候要加上-lpthread选项。
-
在pthread库中通过条件变量来阻塞等待一个条件,或者唤醒等待这个条件的线程。
#include <stdlib.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct _msg{
struct _msg * next;
int num;
} msg;
msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer ( void * p )
{
msg * mp;
for( ;; ) {
pthread_mutex_lock( &lock );
while ( head == NULL )
pthread_cond_wait( &has_product, &lock );
mp = head;
head = mp->next;
pthread_mutex_unlock ( &lock );
printf( "Consume %d tid: %d\n", mp->num, pthread_self());
free( mp );
sleep( rand() % 5 );
}
}
void *producer ( void * p )
{
msg * mp;
for ( ;; ) {
mp = malloc( sizeof(msg) );
pthread_mutex_lock( &lock );
mp->next = head;
mp->num = rand() % 1000;
head = mp;
printf( "Produce %d tid: %d\n", mp->num, pthread_self());
pthread_mutex_unlock( &lock );
pthread_cond_signal( &has_product );
sleep ( rand() % 5);
}
}
int main(int argc, char *argv[] )
{
pthread_t pid1, cid1;
pthread_t pid2, cid2;
srand(time(NULL));
pthread_create( &pid1, NULL, producer, NULL);
pthread_create( &pid2, NULL, producer, NULL);
pthread_create( &cid1, NULL, consumer, NULL);
pthread_create( &cid2, NULL, consumer, NULL);
pthread_join( pid1, NULL );
pthread_join( pid2, NULL );
pthread_join( cid1, NULL );
pthread_join( cid2, NULL );
return 0;
}
count
- 由于结果相互覆盖 ,因此是5000
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
val = counter++;
printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
}
}
countwithmutex
- 引入mutex,防止多线程的冲突
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
pthread_mutex_lock( &counter_mutex );
val = counter++;
printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
pthread_mutex_unlock( &counter_mutex );
}
return NULL;
}
cp_t.c
- 用法:./cp_t [源文件名] [目的文件名] [创建线程数]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <pthread.h>
typedef struct {
char *src_address;
char *dest_address;
int len;
} arg_t;
static void err_sys(const char *s);
static int get_file_length(int fd);
static int extend(int fd, int len);
arg_t map_src_dest(const char *src, const char *dest);
static void *cpy(void *arg);
void divide_thread(int pnum, arg_t arg_file);
int main(int argc, char *argv[])
{
arg_t arg_file;
if (argc != 4) {
fprintf(stderr, "Usage:%s file1 file2 thread_num", argv[0]);
exit(1);
}
arg_file = map_src_dest(argv[1], argv[2]);
divide_thread(atoi(argv[3]), arg_file);
munmap(arg_file.src_address, arg_file.len);
munmap(arg_file.dest_address, arg_file.len);
return 0;
}
static void err_sys(const char *s)
{
perror(s);
exit(1);
}
static int get_file_length(int fd)
{
int position = lseek(fd, 0, SEEK_CUR);
int length = lseek(fd, 0, SEEK_END);
lseek(fd, position, SEEK_SET);
return length;
}
static int extend(int fd, int len)
{
int file_len = get_file_length(fd);
if (file_len >= len) {
return -1;
}
lseek(fd, len - file_len - 1, SEEK_END);
write(fd, "", 1);
return 0;
}
arg_t map_src_dest(const char *src, const char *dest)
{
int fd_src, fd_dest, len;
char *src_address, *dest_address;
arg_t arg_file;
fd_src = open(src, O_RDONLY);
if (fd_src < 0) {
err_sys("open src");
}
len = get_file_length(fd_src);
src_address = mmap(NULL, len, PROT_READ, MAP_SHARED, fd_src, 0);
if (src_address == MAP_FAILED) {
err_sys("mmap src");
}
close(fd_src);
fd_dest = open(dest, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd_dest < 0) {
err_sys("open dest");
}
extend(fd_dest, len);
dest_address = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd_dest, 0);
if (dest_address == MAP_FAILED) {
err_sys("mmap dest");
}
close(fd_dest);
arg_file.len = len;
arg_file.src_address = src_address;
arg_file.dest_address = dest_address;
return arg_file;
}
static void *cpy(void *arg)
{
char *src_address, *dest_address;
int len;
src_address = ((arg_t *) arg)->src_address;
dest_address = ((arg_t *) arg)->dest_address;
len = ((arg_t *) arg)->len;
memcpy(dest_address, src_address, len);
return NULL;
}
void divide_thread(int pnum, arg_t arg_file)
{
int i, len;
char *src_address, *dest_address;
pthread_t *pid;
arg_t arg[pnum];
len = arg_file.len;
src_address = arg_file.src_address;
dest_address = arg_file.dest_address;
pid = malloc(pnum * sizeof(pid));
if (pnum > len) {
fprintf(stderr,
"too many threads, even larger than length, are you crazy?!\n");
exit(1);
}
for (i = 0; i < pnum; i++) {
arg[i].src_address = src_address + len / pnum * i;
arg[i].dest_address = dest_address + len / pnum * i;
if (i != pnum - 1) {
arg[i].len = len / pnum;
} else {
arg[i].len = len - len / pnum * i;
}
pthread_create(&pid[i], NULL, cpy, &arg[i]);
}
for (i = 0; i < pnum; i++) {
pthread_join(pid[i], NULL);
}
free(pid);
}
createthread
- 打印进程和线程ID
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_t ntid;
void printids( const char *s )
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x) \n", s , ( unsigned int ) pid,
( unsigned int ) tid, (unsigned int ) tid);
}
void *thr_fn( void * arg )
{
printids( arg );
return NULL;
}
int main( void )
{
int err;
err = pthread_create( &ntid, NULL, thr_fn, "new thread: " );
if ( err != 0 ){
fprintf( stderr, "can't create thread: %s\n", strerror( err ) );
exit( 1 );
}
printids( "main threads: " );
sleep(1);
return 0;
}
semphore
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
sem_t blank_number, product_number;
void *producer ( void * arg )
{
static int p = 0;
for ( ;; ) {
sem_wait( &blank_number );
queue[p] = rand() % 1000;
printf("Product %d \n", queue[p]);
p = (p+1) % NUM;
sleep ( rand() % 5);
sem_post( &product_number );
}
}
void *consumer ( void * arg )
{
static int c = 0;
for( ;; ) {
sem_wait( &product_number );
printf("Consume %d\n", queue[c]);
c = (c+1) % NUM;
sleep( rand() % 5 );
sem_post( &blank_number );
}
}
int main(int argc, char *argv[] )
{
pthread_t pid, cid;
sem_init( &blank_number, 0, NUM );
sem_init( &product_number, 0, 0);
pthread_create( &pid, NULL, producer, NULL);
pthread_create( &cid, NULL, consumer, NULL);
pthread_join( pid, NULL );
pthread_join( cid, NULL );
sem_destroy( &blank_number );
sem_destroy( &product_number );
return 0;
}
share
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char buf[BUFSIZ];
void *thr_fn1( void *arg )
{
printf("thread 1 returning %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
*(int *)arg = 11;
return (void *) 1;
}
void *thr_fn2( void *arg )
{
printf("thread 2 returning %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
pthread_exit( (void *) 2 );
}
void *thr_fn3( void *arg )
{
while( 1 ){
printf("thread 3 writing %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
sleep( 1 );
}
}
int n = 0;
int main( void )
{
pthread_t tid;
void *tret;
pthread_create( &tid, NULL, thr_fn1, &n);
pthread_join( tid, &tret );
printf("n= %d\n", n );
printf("thread 1 exit code %d\n", (int) tret );
pthread_create( &tid, NULL, thr_fn2, NULL);
pthread_join( tid, &tret );
printf("thread 2 exit code %d\n", (int) tret );
pthread_create( &tid, NULL, thr_fn3, NULL);
sleep( 3 );
pthread_cancel(tid);
pthread_join( tid, &tret );
printf("thread 3 exit code %d\n", (int) tret );
}
本周代码截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 1/2 | 20/20 | |
第二周 | 300/500 | 1/3 | 18/38 | |
第三周 | 150/650 | 1/4 | 20/58 | |
第四周 | 200/850 | 1/5 | 22/80 | |
第五周 | 200/1050 | 1/6 | 24/104 | |
第六周 | 220/1270 | 1/7 | 25/129 | |
第七周 | 150/1300 | 1/8 | 20/149 | |
第八周 | 50/1350 | 2/10 | 21/170 | |
第九周 | 115/1465 | 2/12 | 20/190 | |
第十周 | 720/2185 | 2/14 | 24/214 | |
第十一周 | 481/2666 | 2/16 | 22/226 | |
第十二周 | 147/2813 | 3/19 | 30/256 | |
第十三周 | 1030/3843 | 2/21 | 25/281 |