线程入门,熟悉概念和API






1.26/5 (17投票s)
2005年9月7日
3分钟阅读

43434
线程入门,概念和API
介绍:线程入门。
大家好,我们中的许多人都是伟大的软件工程师,无论如何
我注意到一个事实,我们中的许多人并不清楚
操作系统给我们的伟大礼物。是的
没错,这个礼物叫做多线程环境,
对一些人来说,这些词是噩梦,而对
另一些人来说,这个问题并没有说明太多,我们中的许多人
从未听说过多线程。
本文主要面向初学者和那些不
了解多线程概念的人。
第一部分:什么是线程,
线程是以下内容的集合
{程序计数器、堆栈、CPU},我们
习惯的普通程序只有一个线程,即主线程。我们要做的
是向我们的程序添加一个线程
.
我们将看到创建简单线程的代码示例
,
这是将作为线程运行的函数
void Thread1(int * a_iNum)
{
while(*a_iNum>0)
{
cout<<*a_iNum<<" ";
(*a_iNum)--;
}
}
这是将创建线程的函数
void RunThread1()
{
int l_iNum=10;
HANDLE
l_htRun=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Threa
d1,&l_iNum,0,0);
if(l_htRun==NULL)
{
cout<<"cout could not run the thread \n";
return;
}
WaitForSingleObject(l_htRun,INFINITE);
cout<<"The thread was finished \n";
}
正如我们所见 ,此线程没有执行任何特殊操作
,目标是学习创建线程的API,
我想现在解释一些事情,
LPTHREAD_START_ROUTINE - 这是函数的 typedef,
在 Windows 头文件中,它看起来像这样,
typedef int (*LPTHREAD_START_ROUTINE)(void*);
(LPTHREAD_START_ROUTINE)Thread1 - 这意味着我们
正在对函数类型进行强制转换,以便它
兼容并且代码将被正确编译。
第四个参数是指向我们
要传递给线程的参数的指针。
我现在不会讨论其他参数,我们
稍后会讨论它们。
我们可以看到 CreateThread 函数返回类型
HANDLE,我们对这个 HANDLE 所做的是
等待直到线程完成。
因此,到目前为止,我们了解到创建线程非常容易
我们还没有学到任何有趣的东西
但对于初学者来说,这没关系。
我将要展示的示例对于
线程概念的理解非常重要。
void Thread2(int * a_pNum)
{
while(*a_pNum)
{
cout<<"A";
cout.flush();
::Sleep(100);
}
}
void Thread3(int * a_pNum)
{
while(*a_pNum)
{
cout<<"B";
cout.flush();
::Sleep(100);
}
}
void RunThread2AndThread3()
{
int a_iNum2=1;
int a_iNum3=1;
HANDLE l_hArr[2];
l_hArr[0]
=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread2,&a_i
Num2,0,0);
l_hArr[1]
=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread3,&a_i
Num3,0,0);
WaitForMultipleObjects(2,l_hArr,true,INFINITE);
}
我们可以在此示例中看到的是 2 个正在
运行的线程,其中一个打印 A,第二个打印 B 到
屏幕。
当我在讲授线程时,我的一个学生
问我“这段代码怎么样?”
void SeeTheProblem()
{
int a_iNum2=1;
int a_iNum3=1;
Thread2(&a_iNum2);
a_iNum2=0;
Thread3(&a_iNum3);
a_iNum3=0;
}
它会做同样的工作,不是吗?
正如我们所见,此函数将无法完成工作
我们可以看到线程在它进入函数名称 Thread2 后会陷入停滞
并且它不会
传递此行,因为此函数是无限的。
但是,当我们以线程的形式运行该函数时,它将运行
因为该线程运行在不同的 CPU 上。
注释
1.我添加的 sleep 函数并不那么重要,
如果我没有添加它,结果是你的电脑的 CPU
将是 100%。
2.cout 的 .flush() 是为了将数据刷新到
屏幕,众所周知,cout 不会立即打印
到屏幕,只有当它的缓冲区已满时,所以我
必须刷新日期才能显示 ,这只是为了
这个例子,尽量不要在运行时代码中使用 flush 函数
因为它不是很有效 。
第三部分,使用全局变量,
众所周知,来自一个进程的所有线程都可以访问
进程的数据段,或者换句话说,它们可以
访问全局变量,这是线程的优势之一,也是
问题之一(对于程序员)。
int g_iGoOn=1;
void Thread4(int * a_iNum)
{
while(g_iGoOn)
{
cout<<*a_iNum<<"\n";
::Sleep(5);
}
}
void RunThread4()
{
HANDLE l_hArr[2];
int a_iNum1=10;
int a_iNum2=11;
l_hArr[0]
=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread4,&a_i
Num1,0,0);
l_hArr[1]
=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread5,&a_i
Num2,0,0);
::Sleep(5000);
g_iGoOn=0;
WaitForMultipleObjects(2,l_hArr,true,INFINITE);
}
在这段代码中,我们可以看到线程如何使用
全局变量,以便检查是否继续或
停止他们的活动。
这些例子非常简单,目标是向
用户展示编写代码有多简单,
查看本文的第二部分,以了解
同步问题和解决方案,
在下一篇文章中,我们将更多地讨论这些函数
WaitForSingleObject、WaitForMultipleObject、互斥锁、事件、信号量。