版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、.第十二章 多线程当使用windows 95或者其它现在比较流行的操作系统时,可以同时运行几个程序,这是大家都知道的。操作系统的这种能力称之为多任务处理。现今的许多操作系统也支持线程。一个应用程序能够创建几个线程。线程能够使你在多任务中进行多任务。一般的用户知道他能够在同一时刻运行多个程序,而编程者知道一个程序可以在同一时刻运行几个线程。在本章中,你将学会如何在你的程序中创建和管理线程。具体的说,包含以下内容:l 创建线程l 线程间通信l 线程同步第一节 创建线程线程就是操作系统分配处理器时间的最基本单元。在一个多线程的应用程序中,每一个线程都有它自己的堆栈,并且可以独立的操作在同一程序中运行
2、的其它线程。mfc支持两种线程类型:用户接口线程和工人线程。前者有自己的消息泵,可以处理用户接口的任务,而后者则不能,它是最常用的线程。一个应用程序至少有一个线程,即程序的基本或主线程。你可以根据需要启动和停止其它附加线程,但是一旦主线程停止了,整个程序就被关闭了。只要程序还在运行,主线程就在运行。为了使用mfc创建一个线程,你所做的就是编写一个你希望的和程序的其它部分同时运行的函数,然后调用afxbeginthread()来启动一个用以执行你的函数的线程。只要线程的函数在运行,线程就存活着,当线程函数结束时,线程就被销毁。afxbeginthread()函数如下所示:cwinthread*
3、afxbeginthread( afx_threadproc pfnthreadproc, lpvoid pparam,int npriority = thread_priority_normal, uint nstacksize = 0,dword dwcreateflags = 0, lpsecurity_attributes lpsecurityattrs = null );cwinthread* afxbeginthread( cruntimeclass* pthreadclass,int npriority = thread_priority_normal, uint nstacks
4、ize = 0,dword dwcreateflags = 0, lpsecurity_attributes lpsecurityattrs = null );第一种形式用于创建工人线程,第二种线程用于创建用户接口线程。这两种形式的函数的返回值是新创建的线程对象的指针。参数意义如下:pfnthreadproc:指向工人线程的控制函数的指针,它不能是null。此控制函数必须声明成如下样式:uint mycontrollingfunction( lpvoid pparam );pthreadclass:从cwinthread派生的runtime_classpparam:传递给工人线程的控制函数的参
5、数npriority:线程的期望的优先权。如果这个值为0,则新线程和创建线程具有同样的优先级。nstacksize:以字节为单位定义了新线程的堆栈大小。如果这个值为0,则新线程和创建线程具有同样大小的堆栈。dwcreateflags:控制线程创建的附加标志。这个值可以是以下两个值中的一个:create_suspended和0。如果是标志是前者,以挂起数为1启动线程。只有在resumethread被调用时,这个线程才会被执行。如果标志为0,则在创建线程后立即执行线程。lpsecurityattrs:指向定义了线程安全属性的security_attributes结构的指针。如果为null,则新线程
6、和创建线程具有同样的安全属性。线程可能具有下面的优先级别:thread_priority_above_normal 比正常优先级高一个级别thread_priority_below_normal 比正常优先级低一个级别thread_priority_highest 比正常优先级高两个级别thread_priority_idle 基本优先级为1。对于realtime_priority_class进程,优先级为16。thread_priority_lowest 比正常优先级低两个级别thread_priority_normal 正常优先级别thread_priority_time_critical
7、 基本优先级为15。对于realtime_priority_class进程,优先级别是30。一个线程的优先级决定了相对于其它正在运行的线程这个线程控制系统的时间。通常,线程的级别越高,它的运行时间也越长,这也正是thread_priority_time_critical如此高的原因。下面用一个简单的例子说明如何创建线程,按照下面的步骤进行:使用mfc appwizard生成一个单文档应用程序thread。使用资源编辑器编辑器给程序的idr_mainframe菜单添加一个菜单“线程”。在“线程”菜单中添加一个菜单项启动线程,其id为id_startthread。在cthreadview类中添加消
8、息映射函数onstartthread()。在onstartthread()函数中添加如下代码:void cthreadview:onstartthread()/ todo: add your command handler code herehwnd hwnd = getsafehwnd();afxbeginthread(threadproc, hwnd, thread_priority_normal);添加的代码将调用threadproc(),这个函数是新添加的线程的控制函数,所以还需要在程序中添加这个函数。在threadview.cpp中onstartthread()的上面添加函数thre
9、adproc()。l 注意:l 这个函数是一个全局函数,而并非是cthreadview类的成员函数,尽管它在cthreadview类的执行文件中。在函数threadproc()中添加如下代码:uint threadproc(lpvoid param):messagebox(hwnd)param, thread activated., thread, mb_ok);return 0;这个线程实际上并没有作什么,它仅仅报告它被启动了。在函数前面的两个冒号表明是在调用全局函数,对于windows程序员来说,这通常称为api或sdk调用。当你运行这个程序后,主窗口出现。选择“线程”菜单中的“启动线程”
10、菜单选项,系统启动一个线程,并且显示一个消息框,如图12.1所示。精品.图12. 1 线程启动消息框第二节 线程间通信通常,一个次要的线程为主线程执行一定的任务,这也暗示这在主线程和次要线程之间需要有一个联系的渠道。有几种方法可以完成这些联系任务:使用全局变量、使用ceven类或者使用消息。本节将介绍这几种方法。(1) 使用全局变量通信假定你需要你的程序能够停止线程。你需要一个告诉线程何时停止的方法。一种方法是建立一个全局变量,然后让线程监督这个全局变量是否为标志线程终止的值。为了实现这种方法,按照如下步骤修改前面创建的thread程序。1. 在“线程”菜单中添加菜单项“停止线程”,id为id
11、_stopthread。2. 为“停止线程”添加消息处理函数onstopthread()。3. 在threadview.cpp文件中添加一个全局变量threadcontroller。添加方法是在threadview.cpp的最上面,在endif下面添加下面的语句:volatile int threadcontroller;关键字volatile表示你希望这个变量可以被外面使用它的线程修改。4. 修改onstartthread()函数,代码如下所示:void cthreadview:onstartthread()/ todo: add your command handler code here
12、threadcontroller = 1;hwnd hwnd = getsafehwnd();afxbeginthread(threadproc, hwnd, thread_priority_normal);现在你可能已经猜到threadcontroller的值决定线程是否继续。5. 在onstopthread()函数中添加下列代码:threadcontroller = 0;6. 修改threadproc()函数的代码,代码如下:uint threadproc(lpvoid param):messagebox(hwnd)param, thread activated., thread, mb_
13、ok);while (threadcontroller = 1);:messagebox(hwnd)param, thread stopped., thread, mb_ok);return 0;现_在线程首先显示一个消息框,告诉用户线程被启动了。然后通过一个while循环检查全局变量threadcontroller,等待它的值变成0。尽管这个while循环微不足道,但是你在这里可以加上执行你希望的任务的代码。现在编译并运行这个程序,选择“线程”菜单中的“启动线程”菜单项启动一个线程,这是弹出如图12.1所示的对话框。然后选择“线程”主菜单中的“停止菜单”菜单项,这时弹出如图12.2所示的对话
14、框,告诉用户线程已经终止。图12. 2 线程关闭消息框(2) 使用用户自定义消息通信现在你有了一个简单的用于从主线程中联系附加线程的方法。反过来,如何从附加线程联系主线程呢?最简单的实现这种联系的方法是在程序中加入用户定义的windows消息。首先,要定义用户消息。这一步很容易,例如:const wm_usermsg = wm_user + 100;wm_user变量是由windows定义的,它是第一个有效的用户消息数。因为你的程序的其它部分也会使用用户消息,故将新的用户消息wm_usermsg设置为wm_user+100。在定义了用户消息之后,你应当在线程中调用:postmessage()函
15、数来向主线程发送你所需要的消息。一般按照下面的方式调用:postmessage()函数::postmessage(hwnd)param, wm_usermsg, 0, 0);postmessage()的四个参数分别是接收消息的窗口的句柄、消息的id、消息的wparam和lparam参数。将下面的语句加入到threadview.h中cthreadview类声明的上面。const wm_threadended = wm_user + 100;仍然是在此头文件中,在消息映射中加入下列语句,注意要加到afx_msg的后面和declare_message_map的前面。afx_msg long onth
16、readended();然后切回到threadview.cpp,在类的消息映射中加入下列语句,位置在afx_msg_map之后。on_message(wm_threadended, onthreadended)再用下面的语句更改threadproc()函数。精品.uint threadproc(lpvoid param):messagebox(hwnd)param, thread activated., thread, mb_ok);while (threadcontroller = 1);:postmessage(hwnd)param, wm_threadended, 0, 0);retur
17、n 0;在cthreadview中添加下面的成员函数。long cthreadview:onthreadended(wparam wparam, lparam lparam)afxmessagebox(thread ended.);return 0;图12. 3 线程终止对话框当你重新运行这个程序时,选择“线程”主菜单中的“启动线程”菜单选项,弹出一个消息框告诉你线程已经启动。为了结束这个线程,选择“线程”主菜单中的“停止菜单”菜单选项,这将弹出一个如图12.3所示的消息框告诉你线程已经停止。(3) 使用event对象通信一个比较复杂的在两个线程间通信的方法是使用event对象,在mfc下也就
18、是cevent类对象。一个event对象可以有两种状态:通信状态和非通信状态。线程监视着event对象的状态,并由此在合适的时间执行它们的操作。创建一个cevent类的对象很简单,如下:cevent threadstart;实际上,cevent的构造函数形式如下:cevent( bool binitiallyown = false, bool bmanualreset = false,lpctstr lpszname = null, lpsecurity_attributes lpsaattribute = null );4个参数含义如下:binitiallyown 布尔量。如果值是true,
19、用于cmultilock和csinglelock对象的线程将被允许。如果值为false,所有希望访问资源的线程必须等待。缺省值为false。bmanualreset 布尔量。如果值为true,则event对象是手动对象。如果值为false,则event对象是自动对象。缺省值为true。lpszname cevent对象的名称。如果事件对象被多个进程使用时必须提供一个名称。缺省值为null。lpsaattribute cevent对象的安全属性,与在win32中的security_attributes 相同。尽管cevent的构造函数有4个参数,但是经常不加任何参数的创建缺省的对象。当ceven
20、t对象被创建之后,它自动的处在未通信状态。为了使其处在通信状态,可以调用其成员函数setevent(),如下所示:threadstart.setevent();在执行完上述语句之后,threadstart将处在其通信状态。你的线程应当监视它,这样才能知道何时执行。线程是通过调用如下windows api函数waitforsingleobject()来监视cevent对象的,形式如下::waitforsingleobject(threadstart.m_hobject, infinite);预定义的常量infinite告诉waitforsingleobject()直到指定的cevent对象处在通
21、信状态时才返回。换句话说,如果你把waitforsingleobject()放在线程的开头,系统将挂起线程直到cevent对象处在通信状态。当主线程准备好后,你应当调用setevent()函数。一旦线程不再挂起,它就可以运行了。但是,如果此时你还想和线程通信,线程必须监视下一个cevent对象处在通信状态,故你需要再次调用waitforsingleobject()函数,此时需要将等待时间设置为0,如下所示::waitforsingleobject(threadend.m_hobject, 0);在这种情况下,如果waitforsingleobject()返回值为wait_object_0,则c
22、event对象处在通信状态。否则,cevent对象处在非通信状态。下面的例子说明如何使用cevent类在两个线程间通信。按照以下步骤进行:1. 在threadview.cpp中#include threadview.h语句后面加上#include afxmt.h。2. 在threadview.cpp中volatile int threadcontroller语句后加上下列语句:cevent threadstart;cevent threadend;删除语句volatile int threadcontroller。3. 用下面的代码更换threadproc()函数。uint threadpro
23、c(lpvoid param):waitforsingleobject(threadstart.m_hobject, infinite);:messagebox(hwnd)param, thread activated., thread, mb_ok);bool keeprunning = true;while (keeprunning)int result = :waitforsingleobject(threadend.m_hobject, 0);if (result = wait_object_0)keeprunning = false;:postmessage(hwnd)param,
24、wm_threadended, 0, 0);return 0;精品.4. 用下面的语句替换onstartthread()函数中的内容。threadstart.setevent();5. 用下面的语句替换onstopthread()函数中的内容。threadend.setevent();6. 使用classwizard为cthreadview处理wm_create消息的函数oncreate(),并在todo后面添加代码。oncreate()函数如下所示:int cthreadview:oncreate(lpcreatestruct lpcreatestruct)if (cview:oncreat
25、e(lpcreatestruct) = -1)return -1;/ todo: add your specialized creation code herehwnd hwnd = getsafehwnd();afxbeginthread(threadproc, hwnd);return 0;编译并运行这个程序,新版本的程序运行起来和旧版本的程序一样,但是,新版本的程序为了实现在主线程和次要线程间通信,既使用了cevent类,又使用了用户定义的windows消息。新版本的程序和旧版本的程序的一个大的不同在于次要线程在oncreate()函数中被启动。然而由于线程函数的第一行即调用waitfo
26、rsingleobject(),所以此线程立即被挂起并且等待threadstart处于通信状态。当threadstart处在通信状态时,新线程显示消息框,然后进入while循环。这个while循环继续执行直到threadend处在通信状态,然后线程向主线程发送一个wm_threadended消息并退出。因为此线程是在oncreate()函数中被创建的,一旦结束,不会被重新启动。第三节 线程同步使用多线程可以带来一些非常有趣的问题。例如,如何防止两个线程在同一时间访问同一数据?例如,假设一个线程正在更新一个数据集,而同时另外一个线程正在读取数据集,结果如何?第二个线程将会读取到错误的数据,因为数
27、据集中只有一部分元素被更新过。保持在同一个进程内的线程工作协调一致称之为线程同步。event对象实际上就是线程同步的一种形式。在本节中,你将会学到三种使你的多线程程序更安全的线程同步对象critical section、互斥对象(mutex)、信号量(semaphore)。(1) 使用critical sectioncritical section是一种保证在一个时间只有一个线程访问数据集的非常简单的方法。当你使用critical section,你给了线程一个它们必须共享的对象。任何拥有critical section对象的线程可以访问被保护起来的数据。其它线程必须等待直到第一个线程释放了c
28、ritical section对象,此后其它线程可以按照顺序抢占critical section对象,访问数据。因为线程只有拥有critical section对象才能访问数据,而且在一个时刻只有一个线程可以拥有critical section对象,所以决不会出现一个时刻有多个线程访问数据。为了在mfc程序中创建一个critical section对象,你应当创建ccriticalsection类的对象,如下所示:ccriticalsection criticalsection然后,当程序代码准备访问你保护的数据时,你应当调用ccriticalsection的成员函数lock(),critic
29、alsection.lock();如果另外一个线程并没有拥有criticalsection,lock()将criticalsection给调用它的线程。这个线程便能够访问受保护的数据,此后它调用ccriticalsection的成员函数unlock():criticalsection.unlock();unlock()释放了对criticalsection的拥有权,这样其它线程就可以占有它并访问受保护的数据。最好的方法是将数据放在线程安全类中。当你这样做后,你不用担心在主线程中的线程同步,线程安全类会替你处理的。下面的类ccountarray便是一个线程安全类。以下是countarray.h,
30、ccountarray的头文件。#include afxmt.hclass ccountarrayprivate:int array10;ccriticalsection criticalsection;public:ccountarray() ;ccountarray() ;void setarray(int value);void getarray(int dstarray10);在该头文件中包含一个mfc的头文件afxmt.h,以使程序可以使用ccriticalsection类。在ccountarray类的声明中,头文件声明了一个十个元素的整形数组,这是ccriticalsection类
31、的对象将要保护的数据,并且声明了一个ccriticalsection类的对象criticalsection。ccountarray类的公共成员函数包含构造和析购函数。后面两个成员函数用于访问数据。下面是ccountarray类的执行文件。注意,在每一个成员函数中,ccountarray都在密切关注着ccriticalsection类的对象的状态。这也意味这任何调用这些成员函数的线程不必担心线程同步。例如,如果线程1调用了setarray(),setarray()所做的第一件事就是调用criticalsection.lock(),这将把criticalsection给线程1,此后可以完成一个循环
32、而不用担心被其它线程打断。如果线程2调用了setarray()或getarray(),criticalsection.lock()语句将挂起线程2直到线程1完成循环,执行criticalsection.unlock()语句将对criticalsection的拥有权释放。这时系统唤醒线程2,并将criticalsection给它。通过这种方式,所有线程必须安静的等待它们访问数据的机会到来。下面是countarray.cpp,ccountarray类的执行文件。#include stdafx.h#include countarray.hvoid ccountarray:setarray(int v
33、alue)criticalsection.lock();for (int x=0; x10; +x)精品.arrayx = value;criticalsection.unlock();void ccountarray:getarray(int dstarray10)criticalsection.lock();for (int x=0; x10; +x)dstarrayx = arrayx;criticalsection.unlock();现在你有机会看到线程安全类是什么样式了,现在可以让这个类工作了。按照以下步骤修改前面的thread程序来测试ccountarray。1. 使用file/n
34、ew菜单命令添加一个新的c+头文件countarray.h,并将此头文件加入到thread工程中,并在其中添加代码。2. 再次使用file/new菜单命令添加一个新的c+资源文件countarray.cpp,并在其中添加代码。3. 在threadview.cpp文件中,在#include afxmt.h下面加上:#include countarray.h4. 在threadview.cpp文件中,删除cevent threadstart;和cevent threadend语句,并加上下列语句:ccountarray countarray;5. 从消息映射中删除下列语句:on_message(w
35、m_threadended, onthreadended)on_command(id_stopthread, onstopthread)on_wm_create()6. 用下面两个函数更换threadproc()函数。uint writethreadproc(lpvoid param)for(int x=0; x10; +x)countarray.setarray(x);:sleep(1000);return 0;uint readthreadproc(lpvoid param)int array110;for (int x=0; x20; +x)countarray.getarray(arr
36、ay1);char str50;str0 = 0;for (int i=0; i10; +i)int len = strlen(str);wsprintf(&strlen, %d , array1i);:messagebox(hwnd)param, str, read thread, mb_ok);return 0;7. 用下面的语句替换onstartthread()中的所有语句:void cthreadview:onstartthread()/ todo: add your command handler code herehwnd hwnd = getsafehwnd();afxbegin
37、thread(writethreadproc, hwnd);afxbeginthread(readthreadproc, hwnd);8. 删除函数onstopthread()、onthreadended()、oncreate()。9. 在threadview.h删除下列语句:const wm_threadended = wm_user + 100;10. 在threadview.h中删除下面的语句:afx_msg void onstopthread();afx_msg int oncreate(lpcreatestruct lpcreatestruct);afx_msg long onthr
38、eadended();11. 在资源编辑器中删除“线程”主菜单中的“停止线程”菜单选项。现在编译并运行thread程序的新版本。当运行这个程序后,主窗口首先出现。选择“线程”主菜单中的“启动线程”菜单选项,首先弹出如图12.4所示的消息框显示当前受保护的数组的值。每次当你单击消息框的“确定”按钮,一个新的消息框又出现。这种消息框要出现20次。在消息框中列出的数组的值取决于单击消息框的“确定”按钮以销毁消息框所花的时间,因为第一个线程每一秒更新一次数组中的数据。精品.图12. 4 读取数据对话框如果没有线程同步,则在消息框中出现的数字将不会相同。如果仔细检查源代码,你会发现第一个线程writet
39、hreadproc()在一个循环类十次调用ccountarray类的成员函数setarray()。每一次setarray()函数将criticalsection给这个线程,修改受保护的数组的值,然后又释放对criticalsection的所有权。注意函数sleep(),它将这个线程挂起1000毫秒。第二个线程readthreadproc()为了构造一个显示数组元素的字符串也在访问criticalsection。但是如果writethreadproc()正在用更新数组中元素的值,readthreadproc()必须等待。反之也是对的,即writethreadproc()不能够访问受保护的数据直到
40、它重新从readthreadproc()得到对criticalsection的所有权。如果你希望测试一下criticalsection是否在起作用,把setarray()函数最后的criticalsection.unlock()删除。重新编译并运行该程序,这次没有消息框出现。因为writethreadproc()完全占有了criticalsection,这将导致系统一致将readthreadproc()挂起,直到你退出这个程序。(2) 使用mutex(互斥对象)互斥对象有点象critical section,但有些复杂,因为它不仅允许同一程序的线程之间,而且允许不同程序的线程之间共享资源。尽管
41、在不同程序之间的线程同步超出了本章的范围,但是你可以通过替换critical section获得使用互斥对象的经验。下面是ccountarray2类的头文件。除了名称和互斥对象外,这个文件和原来的ccountarray完全相同。#include afxmt.hclass ccountarray2private:int array10;cmutex mutex;public:ccountarray2() ;ccountarray2() ;void setarray(int value);void getarray(int dstarray10);下面是ccountarray2的执行文件,正如你所
42、看到的,尽管互斥对象和critical section提供相同的服务,但是二者使用起来还是有很多不同的。#include stdafx.h#include countarray2.hvoid ccountarray2:setarray(int value)csinglelock singlelock(&mutex);singlelock.lock();for (int x=0; x10; +x)arrayx = value;void ccountarray2:getarray(int dstarray10)csinglelock singlelock(&mutex);singlelock.lo
43、ck();for (int x=0; x10; +x)dstarrayx = arrayx;为了访问一个互斥对象,你必须创建一个csinglelock对象或一个cmultilock对象,由它们来执行实际上的访问控制。ccountarray2类使用csinglelock对象,因为这个类只处理单一的互斥对象。当代码准备操作受保护的资源,你应当创建一个csinglelock对象,如下所示:csinglelock singlelock(&mutex);这个构造函数的参数是一个指向你希望控制的线程同步对象的指针。接着为了获得对互斥对象的访问权限,你应当调用csinglelock的成员函数lock():s
44、inglelock.lock();如果互斥对象不被任何线程拥有,调用上述语句的线程将拥有该互斥对象。如果另外一个程序已经占有了互斥对象,系统将挂起调用上述语句的线程直到互斥对象被释放,此时被挂起的线程被唤醒并且占有互斥对象。为了释放这个互斥对象,你应该调用csinglelock对象的成员函数unlock()。然而,如果你是在栈中创建的csinglelock对象,就不必调用unlock()。当函数结束后,该对象超出作用范围,这将使其析构函数执行。析构函数将释放互斥对象。为了在thread程序中测试新的类ccountarray2,向工程中添加新的countarray2.h和countarray2.
45、cpp,并删除原来的countarray.h和countarray.cpp。最后在threadview.cpp中将有关ccountarray的代码全部换成ccountarray2,编译并运行程序,结果和使用critical section一样。(3) 使用信号量(semaphore)尽管在mfc程序中使用信号量和使用critical section和互斥对象相差不多,但是功能却不大相同。信号量允许多个线程同时访问资源,但必须是同一点。当你创建了信号量。你应当告诉它同一时刻有多少线程访问它。这样,每次一个线程抢占资源,信号量减小它内部的计数器。当计数器为0时,不会再有其它线程被允许访问资源直到有
46、释放了资源使计数器增加。在创建信号量时应当设置计数器的初始值和最大值,如下所示:csemaphore semaphore(2, 2);因为在本节中你将使用信号量创建线程安全类,因此需要声明一个csemaphore的指针作为成员变量,并且在类的构造函数中创建一个精品.csemaphore对象。semaphore = new csemaphore(2, 2);一旦你创建了信号量对象,就开始计算资源访问。为了实现资源访问,首先应当创建一个csinglelock对象,给它信号量的指针,如下:csinglelock singlelock(semaphore);接着,为了减小信号量的计数器,应当调用csinglelock的成员函数lock(),singlelock.lock();此时,信号量减小了内部的计数器。这个新的数目保持有效直到信号量被释放。通过调用csinglelock的成员函数unlock(),singlelock.unlock();下面是一个新的类csomeresource。csomeresource用来说明信号量的用法。这个类只有一个成员变量即指向csemaphore对象的指针。下面是csomeresource类的头文件someresource.h。#include afxmt.hclass csomeresourceprivate:csemaphore* semapho
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年山西工程科技职业大学马克思主义基本原理概论期末考试模拟题及答案解析(夺冠)
- 2024年涿鹿县招教考试备考题库及答案解析(必刷)
- 2025年嘉鱼县幼儿园教师招教考试备考题库带答案解析(夺冠)
- 2025年四川汽车职业技术学院马克思主义基本原理概论期末考试模拟题含答案解析(夺冠)
- 2025年民乐县幼儿园教师招教考试备考题库附答案解析(必刷)
- 2025年新疆石河子职业技术学院单招职业技能测试题库带答案解析
- 2025年贵州工程应用技术学院马克思主义基本原理概论期末考试模拟题附答案解析(夺冠)
- 2024年湘西民族职业技术学院马克思主义基本原理概论期末考试题附答案解析(夺冠)
- 2025年杭州万向职业技术学院马克思主义基本原理概论期末考试模拟题含答案解析(必刷)
- 2026年湖南工业职业技术学院单招职业倾向性考试题库带答案解析
- 肝性脑病的分级及护理
- 2025年湖北高考真题化学试题(原卷版)
- 2025年中考数学二轮复习专题一 数与式中的化简与计算(含答案)
- T/CECS 10011-2022聚乙烯共混聚氯乙烯高性能双壁波纹管材
- GA/T 2157-2024毛细管电泳遗传分析仪
- 《胰高血糖素抵抗》课件
- 艾滋病实验室课件
- (高清版)AQ 1056-2008 煤矿通风能力核定标准
- 高中名校自主招生考试数学重点考点及习题精讲讲义上(含答案详解)
- 论地理环境对潮汕饮食文化的影响
- 2023年安徽省中考数学试卷及答案详解
评论
0/150
提交评论