1、线程的基本概念
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
优势:
- 易于调度。
- 提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
- 开销少。创建线程比创建进程要快,所需开销很少。。
- 利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。
2、进程的基本状态及状态之间的关系
状态:运行、阻塞、挂起阻塞、就绪、挂起就绪
状态之间的转换:
- 准备就绪的进程,被CPU调度执行,变成运行态;
- 运行中的进程,进行I/O请求或者不能得到所请求的资源,变成阻塞态;
- 运行中的进程,进程执行完毕(或时间片已到),变成就绪态;
- 将阻塞态的进程挂起,变成挂起阻塞态,当导致进程阻塞的I/O操作在用户重启进程前完成(称之为唤醒),挂起阻塞态变成挂起就绪态,当用户在I/O操作结束之前重启进程,挂起阻塞态变成阻塞态;
- 将就绪(或运行)中的进程挂起,变成挂起就绪态,当该进程恢复之后,挂起就绪态变成就绪态;
3、线程和进程的关系以及区别
进程和线程的关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
- 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
- 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
4、进程间通信的方式
- 管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
- 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
- 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
- 共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
- 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
- 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
5、同步和互斥的区别
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
6、多线程共享、独占资源
进程是资源分配的基本单位;线程是系统调度的基本单位。
平时我们写的程序都是作为线程运行的;进程可以看做是包括一系列线程和资源的统称;一个进程至少包括一个线程(主线程,进入main函数时产生的);在其中可以创建其它线程,也可以不创建。
共享资源
a. 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
c. 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
d. 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
独享资源
a. 栈 栈是独享的
b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
7、同步、异步的概念
(1)同步
进程同步就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事.就像早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭.按照这个定义,其实绝大多数函数都是同步调用(例如sin,isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
最常见的例子就是sendmessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的lresult值返回给调用者。
(2)异步
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
以casycsocket类为例(注意,csocket从casyncsocket派生,但是其功能已经由异步转化为同步),当一个客户端通过调用connect函数发出一个连接请求后,调用者线程立刻可以朝下运行。当连接真正建立起来以后,socket底层会发送一个消息通知该对象。
这里提到执行部件和调用者通过三种途径返回结果:状态、通知和回调。可以使用哪一种依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一种很严重的错误)。如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
8、demo
(1)一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。
inttally = 0;//glable
voidThreadProc()
{
for(inti = 1;i <= 50;i++)
tally += 1;
}
答:[50,100]
(2)生产者消费者问题
问题描述:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
首先来简化问题,先假设生产者和消费者都只有一个,且缓冲区也只有一个。这样情况就简便多了。
第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段和互斥量来完成。
第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件或信号量来控制。
代码引自 moreWindows,也正是最下面这个博客的作者。
//1生产者 1消费者 1缓冲区
//使用二个事件,一个表示缓冲区空,一个表示缓冲区满。
//再使用一个关键段来控制缓冲区的访问
#include <stdio.h>
#include <process.h>
#include <windows.h>
//设置控制台输出颜色
BOOL SetConsoleColor(WORD wAttributes)
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return FALSE;
return SetConsoleTextAttribute(hConsole, wAttributes);
}
const int END_PRODUCE_NUMBER = 10; //生产产品个数
int g_Buffer; //缓冲区
//事件与关键段
CRITICAL_SECTION g_cs;
HANDLE g_hEventBufferEmpty, g_hEventBufferFull;
//生产者线程函数
unsigned int __stdcall ProducerThreadFun(PVOID pM)
{
for (int i = 1; i <= END_PRODUCE_NUMBER; i++)
{
//等待缓冲区为空
WaitForSingleObject(g_hEventBufferEmpty, INFINITE);
//互斥的访问缓冲区
EnterCriticalSection(&g_cs);
g_Buffer = i;
printf("生产者将数据%d放入缓冲区\n", i);
LeaveCriticalSection(&g_cs);
//通知缓冲区有新数据了
SetEvent(g_hEventBufferFull);
}
return 0;
}
//消费者线程函数
unsigned int __stdcall ConsumerThreadFun(PVOID pM)
{
volatile bool flag = true;
while (flag)
{
//等待缓冲区中有数据
WaitForSingleObject(g_hEventBufferFull, INFINITE);
//互斥的访问缓冲区
EnterCriticalSection(&g_cs);
SetConsoleColor(FOREGROUND_GREEN);
printf(" 消费者从缓冲区中取数据%d\n", g_Buffer);
SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
if (g_Buffer == END_PRODUCE_NUMBER)
flag = false;
LeaveCriticalSection(&g_cs);
//通知缓冲区已为空
SetEvent(g_hEventBufferEmpty);
Sleep(10); //some other work should to do
}
return 0;
}
int main()
{
printf(" 生产者消费者问题 1生产者 1消费者 1缓冲区\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
InitializeCriticalSection(&g_cs);
//创建二个自动复位事件,一个表示缓冲区是否为空,另一个表示缓冲区是否已经处理
g_hEventBufferEmpty = CreateEvent(NULL, FALSE, TRUE, NULL);
g_hEventBufferFull = CreateEvent(NULL, FALSE, FALSE, NULL);
const int THREADNUM = 2;
HANDLE hThread[THREADNUM];
hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);
hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);
WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
//销毁事件和关键段
CloseHandle(g_hEventBufferEmpty);
CloseHandle(g_hEventBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}
运行结果:
更多多线程内容
http://blog.csdn.net/morewindows/article/details/7392749