一:进程的相关概念:
1、进程是操作系统中最古老也是最重要的概念之一,操作系统的所有其它内容也是围绕着进程的概念展开的,所以透彻的理解进程是非常重要的。
操作系统中最核心的概念是进程:进程是对正在运行程序的一个抽象。
区分伪并行、多处理系统
伪并行:在多道程序设计的系统中cpu由一个进程快速切换到另一个进程,每个进程运行几十或几百个毫秒,这样在1秒中期间,它可能运行多个进程,这样就产生并行的错觉,但是在某一个瞬间,cpu只能运行一个进程。
多处理系统:系统有两个或多个cpu来共享物理内存,多处理器系统实现了真正的硬件并行。(一个cpu只能真正一次运行一个进程,即使有多个核或cpu,每个核一次只能运行一个进程)
进程的关键思想:
一个进程是某种类型的的一个活动,他有程序、输入、输出以及状态。进程和程序之间是有本质区别的。单个处理器可以被若干进程共享,它使用某种调度算法决定何时停止一个进程的工作,并转而为另外一个进程工作。。注意:一个程序执行两遍算作两个进程,OS能够是他们共享代码,因此只有一个副本放在内存中。
2、创建进程:
4中主要的事件导致进程的创建:
1)系统的初始化
2)执行了正在运行的进程所调用的进程创建系统调用
3)用户请求创建一个新进程
4)一个批处理作业的初始化
3、进程的终止:
进程创建之后,他开始运行,完成其工作,最后终止。进程的终止通常由一下条件引起:
1)正常退出(自愿)
2)出错退出(自愿)
3)严重错误(非自愿)
4)被其它进程杀死(非自愿)
二、线程相关概念
线程:在一个程序内部也可以实现多个任务并发执行,其中每个任务称为线程。
线程是比进程更小的执行单位,它是在一个进程中独立的控制流,即程序内部的控制流。
特点:线程不能独立运行,必须依赖于进程,在进程中运行。
每个程序至少有一个线程称为主线程。
单线程:只有一条线程的进程称为单线程
多线程:有不止一个线程的进程称为多线程(eg:字处理软件可以被编写成含有三个线程的程序,一个线程与用户交互另一个线程在后台执行格式处理,第三个线程可以处理磁盘备份,而不必干扰其它两个线程)
进程中不同的线程不向不同进程之间那样存在很高的独立性,统一进程之间的所有线程都有完全一样的地址空间,这就意味着他们也共享同样的全局变量,由于各个线程都可以访问进程地址空间的每一个内存地址,所以一个线程可以读、写甚至清除另一个线程的堆栈。线程之间是没有保护的(但是有锁)。这与不同进程是有差别的。不同进程会来自不同的用户,彼此之间不会产生敌意,一个进程有某个用户创建,而该用户创建多个线程是为了让他们合作而非彼此斗争。
三、多线程编程实例
1、POSIX线程
POSIX线程是线程的POSIX标准,定义了创建和操纵线程的一套API。
实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-like POSIX 系统,如Linux、 Solaris。但是Microsoft Windows上的实现也存在。
1)、Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因此,后面的编译必须在选项中加入 -lpthread选项,否则提示找不到pthread_create()这些函数。
2)、pthread_t 是一个线程的标识符,创建线程用pthread_create(),等待线程结束用pthread_join(),。下面简单介绍一下Pthread的函数调用:
线程调用 | 描述 |
pthread_create() | 创建一个新的线程 |
pthread_exit() | 结束调用的线程 |
pthread_join() | 等待一个特殊的线程退出 |
pthread_yield() | 释放cpu来运行另外一个线程 |
pthread_attr_init() | 创建并初始化一个线程的属性结构 |
pthread_attr_destory() | 删除一个线程的属性结构 |
当线程完成分配给他的工作时,可以通过pthread_exit()。这个调用终止该线程并释放它的栈。
一般一个线程在继续运行前需要等待另一个线程完成它的工作并退出。可以通过pthread_join()线程调用来等待别的特定的线程的终止,而要等待的线程的标识符作为一个参数输出。
pthread_yield():假设一个线程长期占用cpu,而你想给另一个线程一个运行的机会可以调用该函数完成。
pthread_attr_init():建立关联一个线程的属性结构并初始化为默认值。这些值可以通过属性结构中的域值来改变。
pthread_attr_destory():删除一个线程的属性结构,释放它占用的内存。他不会影响调用它的线程。这些线程会继续存在。
3)eg:
#include
#include
void thread_print_1()
{
int i;
for(i=0;i<3;i++)
{
printf("I am thread one %d\n",i);
sleep(i);
}
}
void thread_print_2()
{
int i;
for(i=0;i<3;i++)
{
printf("I am thread two %d\n",i);
sleep(i);
}
}
int main(int argc,char **argv)
{
pthread_t id_1,id_2;
int i,ret_1,ret_2;
ret_1=pthread_create(&id_1,NULL,(void *)thread_print_1,NULL);
if(ret_1!=0) //pthread_create创建线程,返回值是0,表示成功
{
printf("create thread_1 error\n");
exit(1);
}
ret_2=pthread_create(&id_2,NULL,(void *)thread_print_2,NULL);
if(ret_2!=0)
{
printf("create thread_2 error\n");
exit(1);
}
for(i=0;i<3;i++)
{
printf("I am main %d\n",i);
sleep(i);
}
pthread_join(id_1,NULL);
pthread_join(id_2,NULL);
return(0);
}
使用gcc -g -lpthread simpleone.c 编译
使用./a.out运行
结果:
I am main 0 不同的机器运行结果有可能不同,是由于三个“并行”的线程抢夺处理器资源造成的。而sleep(i)仅仅是为了让结果看上去更加能体现这三个线程是“并行”执行的。在上面的例子中,加入了pthread.h头文件,这是所有pthread多线程程序所必须的,接着就是两个函数的定义,这和普通的函数没有什么区别。
void thread_print_1();void thread_print_2();这两个函数将被用做线程的执行体。
pthread_t是线程结构,用来保存线程相关数据,可以理解为是线程类型,声明一个线程对象(变量)。程序代码 “pthread_t id_1,id_2”,声明了两个线程变量id_1,id_2
程序代码:ret_1=pthread_create(&id_1,NULL,(void *)thread_print_1,NULL);
ret_2=pthread_create(&id_2,NULL,(void *)thread_print_2,NULL);
这两句非常重要,pthread_create用来创建线程并启动,原型是:
int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);
上述函数中第一个参数是线程指针,第二参数是线程属性指针,线程属性pthread_attr_t用来指定线程优先级等属性,一般的情况下,没有必要修改,使用默认属性来构造线程,所以这里一般取NULL,第三个参数是一个函数指针,就是线程要执行的代码,这里我们分别要执行thread_print_1()thread_print_2(),这里这个函数指针的类型定义是返回一个空类型指针,接收一个空类型指针参数的函数指针,如果你的函数不是这个定义,那就可以直接转化一下就可以了。
写完这两行代码,两个线程就已经执行起来了,如果你省略了
程序代码"pthread_join(id,NULL);"然后编译运行程序的时候会发现程序似乎什么也没干就退出了,那是因为程序的主线程退出的时候操作系统会关闭应用程序使用的所有资源,包括线程所以在main函数结束前我们得想办法让程序停下来,pthread_join方法的功能就是等待线程结束,要等的线程就是第一个参数,程序会在这个地方停下来,直到线程结束,第二个参数用来接受线程函数的返回值,是void**类型的指针,如果没有返回值,就直接设为NULL吧。
四:多线程解决生产者——消费者问题
一个线程的代码就像进程一样,通常会包含多个过程,会包含局部变量、全局变量和过程参数。局部变量和参数不会引起任何问题,但是在全局变量的访问上会出问题,例如:在定票系统中,两个进程同时为不同的客户争取最后一个座位,A已经获取资源但是还没来得及修改相应的全局变量,这时B读取到还有座位他就会执行订票操作但是到最后会出现错误。
1)解决方法
互斥量:是一个可以处于两态之一的变量,解锁和加锁。这样只需要一个二进制位表示即可,一般用一个整形量表示:0表示解锁,其他所有值表示加锁。互斥量使用两个过程,当一个线程访问临界区时,调用mutex_lock,mutex_unlock进行解锁。
一些与互斥量相关的Pthread调用:
线程调用 | 描述 |
pthread_mutex_init | 创建一个互斥量 |
pthread_mutex_destroy | 撤销一个已经存在的互斥量 |
pthread_mutex_lock | 获得一个锁或阻塞 |
pthread_mutex_unlock | 释放锁 |
除了互斥量,pthread还提供另一种同步机制:条件变量。互斥量在允许或阻塞对临界区的访问,条件变量则允许线程由于一些未达到的条件而阻塞。但是绝大部分情况下两种方法一起使用。
一些与条件变量相关的Pthread调用:
pthread_cond_init | 创建条件变量 |
pthread_cond_destroy | 撤销一个条件变量 |
pthread_cond_wait | 阻塞以等待一个信号 |
pthread_cond_signal | 向另外一个线程发信号来唤醒它 |
pthread_cond_wait 在不同条件下行为不同:
Eg:
同是使用互斥量和条件变量的例子,生产者——消费者问题(单缓冲区)
#include
#include
#define MAX 10 //需要生产的数量
pthread_mutex_t the_mutex; //定义互斥锁
pthread_cond_t condc,condp; //条件变量
int buffer=0; //缓冲区定义一次只能写入一个数据
void *producer(void *ptr) // 生产者生产数据
{
int i;
for(i=1;i<=MAX;i++)
{
pthread_mutex_lock(&the_mutex); //互斥使用缓冲区
while(buffer!=0) pthread_cond_wait(&condp,&the_mutex); //当前没有可用缓冲区,阻塞自己
buffer=i;
printf("生产者生产i=%d \n",i);
sleep(i);
printf("hello\n");
pthread_cond_signal(&condc); //唤醒消费者
pthread_mutex_unlock(&the_mutex);
}
pthread_exit(0);
}
void *consumer(void *ptr)
{
int i;
for(i=1;i<=MAX;i++)
{
pthread_mutex_lock(&the_mutex);
while(buffer==0) pthread_cond_wait(&condc,&the_mutex); //当前缓冲区为空,阻塞自己
buffer=0;
printf("消费者购买产品 %\n",0);
sleep(i);
pthread_cond_signal(&condp); //唤醒生产者
pthread_mutex_unlock(&the_mutex);
}
pthread_exit(0);
}
int main(int argc,char **argv)
{
pthread_t pro,con;//定义两个线程
pthread_mutex_init(&the_mutex,0);//初始化互斥锁
pthread_cond_init(&condc,0);//初始化条件变量
pthread_cond_init(&condp,0);//初始化条件变量
pthread_create(&con,NULL,consumer,NULL);//创建并启动线程
pthread_create(&pro,NULL,producer,NULL);//创建并启动线程
pthread_join(pro,0);
pthread_join(con,0);
pthread_cond_destroy(&condc);//撤销条件变量
pthread_cond_destroy(&condp);
pthread_mutex_destroy(&the_mutex);
}
wangye@wangye:~/wangye_study/C_Study/thread_example$ gcc -g -lpthread r_w.c
wangye@wangye:~/wangye_study/C_Study/thread_example$ ./a.out
结果:
生产者生产i=1
hello
消费者购买产品 %
生产者生产i=2
hello
消费者购买产品 %
生产者生产i=3
hello
消费者购买产品 %
生产者生产i=4
hello
消费者购买产品 %
生产者生产i=5
hello
消费者购买产品 %
生产者生产i=6
hello
消费者购买产品 %
生产者生产i=7
hello
消费者购买产品 %
生产者生产i=8
hello
消费者购买产品 %
生产者生产i=9
hello
消费者购买产品 %
生产者生产i=10
hello
消费者购买产品 %
Eg:生产者——消费者问题(多缓冲区)
#include "stdio.h"
#include
#include
#define N_CONSUMER 3 //消费者数量
#define N_PRODUCER 2 //生产者数量
#define C_SLEEP 1 //控制 consumer 消费的节奏
#define P_SLEEP 1 //控制 producer 生产的节奏
pthread_t ctid[N_CONSUMER];//消费者线程数组
pthread_t ptid[N_PRODUCER];//生产者线程数组
pthread_cond_t notFull,notEmpty;//定义条件变量,notFull缓冲区不满;notEmpty缓冲区不空
pthread_mutex_t buf = PTHREAD_MUTEX_INITIALIZER;//静态分配互斥量,用于锁住缓冲区
int begin = 0,end = 0, cnt = 0, max = 4;//从 begin 到 end(不含end) 代表产品,cnt 代表产品数量,max 代
表库房的容量,即最多生产多少产品
void * consumer(void * pidx)//consumer thread idx
{
printf("consumer thread id %d\n",*((int *)pidx));
while(1)
{
pthread_mutex_lock(&buf);
while(cnt == 0){//当缓冲区空时
pthread_cond_wait(¬Empty,&buf);//阻塞并等待不空的信号
}
printf("consume %d\n",begin);
begin = (begin+1)%max;
cnt--;
pthread_mutex_unlock(&buf);
sleep(C_SLEEP);
pthread_cond_signal(¬Full);
}
pthread_exit((void *)0);
}
void * producer(void * pidx)//producer thread idx
{
printf("producer thread id %d\n",*((int *)pidx));
while(1)
{
pthread_mutex_lock(&buf);
while(cnt == max){//当缓冲区满时
pthread_cond_wait(¬Full,&buf);
}
pthread_mutex_lock(&buf);
while(cnt == 0){//当缓冲区空时
pthread_cond_wait(¬Empty,&buf);//阻塞并等待不空的信号
}
printf("consume %d\n",begin);
begin = (begin+1)%max;
cnt--;
pthread_mutex_unlock(&buf);
sleep(C_SLEEP);
pthread_cond_signal(¬Full);
}
pthread_exit((void *)0);
}
void * producer(void * pidx)//producer thread idx
{
printf("producer thread id %d\n",*((int *)pidx));
while(1)
{
pthread_mutex_lock(&buf);
while(cnt == max){//当缓冲区满时
pthread_cond_wait(¬Full,&buf);
}
int main()
{
int i = 0;
for(i = 0; i < N_CONSUMER; i++)
{
int * j = (int *) malloc(sizeof(int));
*j = i;
if(pthread_create(&ctid[i],NULL,consumer,j) != 0)
{
perror("create consumer failed\n");
exit(1);
}
}
for(i = 0; i < N_PRODUCER; i++)
{
int * j = (int *) malloc(sizeof(int));
*j = i;
if(pthread_create(&ptid[i],NULL,producer,j) != 0)
{
perror("create producer failed\n");
exit(1);
}
}
while(1)
{
sleep(10);
}
return 0;
}
编译:gcc -g -lpthread r_w_1.c
运行:./a.out
到这里线程的学习就告一段落了,但是上面涉及到的函数相关内容没有仔细说明,后续还会补充的。。。。。。。。。。。