多线程与互斥对象

一、程序、进程与线程。

1、程序是计算机指令的集合,它以文件的形式存储在磁盘上。
2、进程:通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。
3、进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。
4、进程由两个部分组成:
a、操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。
b、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。

5、进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。
6、单个进程可能包含若干个线程,这些线程都“同时” 执行进程地址空间中的代码。

7、每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程。此后,该线程可以创建其他的线程。

8、系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个地址空间是4GB。

9、每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0x12345678,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0x12345678。当进程A中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。
10、4GB是虚拟的地址空间,只是内存地址的一个范围。在你能成功地访问数据而不会出现非法访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。
11、4GB虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓冲、非页面内存池的分配和进程页面表等使用,而用户方式分区使用的地址空间约为2GB,这个分区是进程的私有地址空间所在的地方。一个进程不能读取、写入、或者以任何方式访问驻留在该分区中的另一个进程的数据。对于所有应用程序来说,该分区是维护进程的大部分数据的地方。

12、线程由两个部分组成:
a、线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
b、线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。
13、当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。 
14、线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。 
15、线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。16、因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。

17、操作系统为每一个运行线程安排一定的CPU时间 —— 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。
18、如果计算机拥有多个CPU,线程就能真正意义上同时运行了。

CreateThread

The CreateThread function creates a thread to execute within the virtual address space of the calling process.

To create a thread that runs in the virtual address space of another process, use theCreateRemoteThread function.

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
  SIZE_T dwStackSize,                       // initial stack size
  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function
  LPVOID lpParameter,                       // thread argument
  DWORD dwCreationFlags,                    // creation option
  LPDWORD lpThreadId                        // thread identifier
);
Parameters
lpThreadAttributes
[in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited.

Windows NT/2000/XP: The lpSecurityDescriptor member of the structure specifies a security descriptor for the new thread. IflpThreadAttributes is NULL, the thread gets a default security descriptor.

指向SECURITY_ATTRIBUTES结构体的指针。这里可以设置为NULL,使用默认的安全性。

dwStackSize
[in] Specifies the initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is zero, the new thread uses the default size for the executable. For more information, see Thread Stack Size.

指定初始提交的栈的大小,即线程可以将多少地址空间用于自己的栈,以字节为单位。系统会将这个值四舍五入为最近的页面。

如果该值是0或者小于缺省提交大小,则使用和调用线程一样的大小。

页面是系统管理内存时使用的内存单位,不同的CPU其页面大小也是不同的。X86 使用的页面大小是4KB。当保留地址空间的一个区域时,系统要确保该区域的大小是系统的页面大小的倍数。

lpStartAddress
[in] Pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread. For more information on the thread function, see ThreadProc.

指向LPTHREAD_START_ROUTINE(应用程序定义的函数类型)的指针。这个函数将被线程执行,表示了线程的起始地址,指定线程入口函数,该入口函数的参数类型以及返回类型要与ThreadProc()函数声明的类型要保持一致。

lpParameter
[in] Specifies a single parameter value passed to the thread.
指定传递给线程的单独的参数的值。
dwCreationFlags
[in] Specifies additional flags that control the creation of the thread. If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state, and will not run until the ResumeThread function is called. If this value is zero, the thread runs immediately after creation. At this time, no other values are supported.

Windows XP: If the STACK_SIZE_PARAM_IS_A_RESERVATION flag is specified, thedwStackSize parameter specifies the initial reserve size of the stack. Otherwise,dwStackSize specifies the commit size.

指定控制线程创建的附加标记。

如果CREATE_SUSPENDED标记被指定,线程创建后处于暂停状态,不会运行,直到调用了ResumeThread函数。    

如果该值是0,线程在创建之后立即运行。

lpThreadId
[out] Pointer to a variable that receives the thread identifier.

Windows NT/2000/XP: If this parameter is NULL, the thread identifier is not returned.

Windows 95/98/Me: This parameter may not be NULL.

[out]指向一个变量用来接收线程的标识符。创建一个线程时,系统会为线程分配一个ID号。

Windows NT/2000:如果这个参数是NULL,线程的标识符不会返回。

Windows 95/98  :这个参数不能是NULL。

如果线程创建成功,此函数返回线程的句柄。

Return Values

If the function succeeds, the return value is a handle to the new thread.

If the function fails, the return value is NULL. To get extended error information, callGetLastError.

Note that CreateThread may succeed even if lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process. This behavior is similar to the asynchronous nature ofCreateProcess, where the process is created even if it refers to invalid or missing dynamic-link libraries (DLLs).

Windows 95/98/Me: CreateThread succeeds only when it is called in the context of a 32-bit program. A 32-bit DLL cannot create an additional thread when that DLL is being called by a 16-bit program.

Remarks

The number of threads a process can create is limited by the available virtual memory. By default, every thread has one megabyte of stack space. Therefore, you can create at most 2028 threads. If you reduce the default stack size, you can create more threads. However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information. A thread would process all requests in a queue before processing requests in the next queue.

The new thread handle is created with THREAD_ALL_ACCESS to the new thread. If a security descriptor is not provided, the handle can be used in any function that requires a thread object handle. When a security descriptor is provided, an access check is performed on all subsequent uses of the handle before access is granted. If the access check denies access, the requesting process cannot use the handle to gain access to the thread. If the thread impersonates a client, then callsCreateThread with a NULL security descriptor, the thread object created has a default security descriptor which allows access only to the impersonation token's TokenDefaultDacl owner or members. For more information, seeThread Security and Access Rights.

The thread execution begins at the function specified by the lpStartAddress parameter. If this function returns, theDWORD return value is used to terminate the thread in an implicit call to theExitThread function. Use the GetExitCodeThread function to get the thread's return value.

The thread is created with a thread priority of THREAD_PRIORITY_NORMAL. Use theGetThreadPriority and SetThreadPriority functions to get and set the priority value of a thread.

When a thread terminates, the thread object attains a signaled state, satisfying any threads that were waiting on the object.

The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call toCloseHandle.

The ExitProcess, ExitThread, CreateThread, CreateRemoteThread functions, and a process that is starting (as the result of a call byCreateProcess) are serialized between each other within a process. Only one of these events can happen in an address space at a time. This means that the following restrictions hold:

  • During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is done for the process.
  • Only one thread in a process can be in a DLL initialization or detach routine at a time.
  • ExitProcess does not return until no threads are in their DLL initialization or detach routines.

A thread that uses functions from the C run-time libraries should use the beginthread and endthread C run-time functions for thread management rather thanCreateThread and ExitThread. Failure to do so results in small memory leaks whenExitThread is called. 


线程函数:

ThreadProc

The ThreadProc function is an application-defined function that serves as the starting address for a thread. Specify this address when calling theCreateThread or CreateRemoteThread function. The LPTHREAD_START_ROUTINE type defines a pointer to this callback function.ThreadProc is a placeholder for the application-defined function name.

DWORD WINAPI ThreadProc(
  LPVOID lpParameter   // thread data
);
Parameters
lpParameter
[in] Receives the thread data passed to the function using the lpParameter parameter of the CreateThread or CreateRemoteThread function.
Return Values

The function should return a value that indicates its success or failure.

Remarks

A process can obtain the return value of the ThreadProc of a thread it created withCreateThread by calling the GetExitCodeThread function. A process cannot obtain the return value from theThreadProc of a thread it created with CreateRemoteThread


暂停线程执行:

当线程暂停执行的时候,也就是表示它放弃了执行的权力。
操作系统会从等待运行的线程队列中选择一个线程来运行。新创建的线程就可以得到运行的机会。
可以使用函数Sleep:

Sleep

The Sleep function suspends the execution of the current thread for the specified interval.

To enter an alertable wait state, use the SleepEx function.

VOID Sleep(
  DWORD dwMilliseconds   // sleep time,以毫秒为单位
);
Parameters
dwMilliseconds
[in] Specifies the time, in milliseconds, for which to suspend execution. A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution. A value of INFINITE causes an infinite delay.
Return Values

This function does not return a value.

Remarks

A thread can relinquish the remainder of its time slice by calling this function with a sleep time of zero milliseconds.

You have to be careful when using Sleep and code that directly or indirectly creates windows. If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. If you have a thread that uses Sleep with infinite delay, the system will deadlock. Two examples of code that indirectly creates windows are DDE and COMCoInitialize. Therefore, if you have a thread that creates windows, useMsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than Sleep.

eg:

HANDLE hThread;
hThread=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
CloseHandle(hThread);
Sleep(10);
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    ;
}

</pre><p></p><pre>
二、互斥对象。

1、互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。2、互斥对象包含一个使用数量,一个线程ID和一个计数器。3、ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

CreateMutex

The CreateMutex function creates or opens a named or unnamed mutex object.

HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,  // SD
  BOOL bInitialOwner,                       // initial owner
  LPCTSTR lpName                            // object name
);
Parameters
lpMutexAttributes
[in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpMutexAttributes is NULL, the handle cannot be inherited.
指向SECURITY_ATTRIBUTES结构体的指针。可以传递NULL,让其使用默认的安全性。
 

Windows NT/2000/XP: The lpSecurityDescriptor member of the structure specifies a security descriptor for the new mutex. IflpMutexAttributes is NULL, the mutex gets a default security descriptor.

bInitialOwner
[in] Specifies the initial owner of the mutex object. If this value is TRUE and the caller created the mutex, the calling thread obtains ownership of the mutex object. Otherwise, the calling thread does not obtain ownership of the mutex. To determine if the caller created the mutex, see the Return Values section.
指示互斥对象的初始拥有者。 如果该值是真,调用者创建互斥对象,调用的线程获得互斥对象的所有权。 否则,调用线程捕获的互斥对象的所有权。(就是说,如果该参数为真,则调用该函数的线程拥有互斥对象的所有权。否则,不拥有所有权,当前互斥对象处于空闲状态,其他线程可以占用)
lpName
[in] Pointer to a null-terminated string specifying the name of the mutex object. The name is limited to MAX_PATH characters. Name comparison is case sensitive.
互斥对象名称。传递NULL创建的就是没有名字的互斥对象,即匿名的互斥对象。
 

If lpName matches the name of an existing named mutex object, this function requests MUTEX_ALL_ACCESS access to the existing object. In this case, thebInitialOwner parameter is ignored because it has already been set by the creating process. If thelpMutexAttributes parameter is not NULL, it determines whether the handle can be inherited, but its security-descriptor member is ignored.

If lpName is NULL, the mutex object is created without a name.

If lpName matches the name of an existing event, semaphore, waitable timer, job, or file-mapping object, the function fails and theGetLastError function returns ERROR_INVALID_HANDLE. This occurs because these objects share the same name space.

Terminal Services: The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session name space. The remainder of the name can contain any character except the backslash character (\). For more information, see Kernel Object Name Spaces.

Windows XP: Fast user switching is implemented using Terminal Services sessions. The first user to log on uses session 0, the next user to log on uses session 1, and so on. Kernel object names must follow the guidelines outlined for Terminal Services so that applications can support multiple users.

Windows 2000: If Terminal Services is not running, the "Global\" and "Local\" prefixes are ignored. The remainder of the name can contain any character except the backslash character.

Windows NT 4.0 and earlier: The name can contain any character except the backslash character.

Windows 95/98/Me: The name can contain any character except the backslash character. The empty string ("") is a valid object name.

Return Values

If the function succeeds, the return value is a handle to the mutex object. If the named mutex object existed before the function call, the function returns a handle to the existing object andGetLastError returns ERROR_ALREADY_EXISTS. Otherwise, the caller created the mutex.

If the function fails, the return value is NULL. To get extended error information, callGetLastError.

创建成功之后 ,返回一个互斥对象句柄。如果一个命名的互斥对象在本函数调用之前已经存在,则返回已经存在的对象句柄。然后可以调用GetLastError检查其返回值是否为ERROR_ALREADY_EXISTS,TRUE则表示命名互斥对象已经存在,否则表示互斥对象是新创建的。

Remarks

The handle returned by CreateMutex has MUTEX_ALL_ACCESS access to the new mutex object and can be used in any function that requires a handle to a mutex object.

Any thread of the calling process can specify the mutex-object handle in a call to one of thewait functions. The single-object wait functions return when the state of the specified object is signaled. The multiple-object wait functions can be instructed to return either when any one or when all of the specified objects are signaled. When a wait function returns, the waiting thread is released to continue its execution.

The state of a mutex object is signaled when it is not owned by any thread. The creating thread can use thebInitialOwner flag to request immediate ownership of the mutex. Otherwise, a thread must use one of the wait functions to request ownership. When the mutex's state is signaled, one waiting thread is granted ownership, the mutex's state changes to nonsignaled, and the wait function returns. Only one thread can own a mutex at any given time. The owning thread uses theReleaseMutex function to release its ownership.

The thread that owns a mutex can specify the same mutex in repeated wait function calls without blocking its execution. Typically, you would not wait repeatedly for the same mutex, but this mechanism prevents a thread from deadlocking itself while waiting for a mutex that it already owns. However, to release its ownership, the thread must callReleaseMutex once for each time that the mutex satisfied a wait.

Two or more processes can call CreateMutex to create the same named mutex. The first process actually creates the mutex, and subsequent processes open a handle to the existing mutex. This enables multiple processes to get handles of the same mutex, while relieving the user of the responsibility of ensuring that the creating process is started first. When using this technique, you should set thebInitialOwner flag to FALSE; otherwise, it can be difficult to be certain which process has initial ownership.

Multiple processes can have handles of the same mutex object, enabling use of the object for interprocess synchronization. The following object-sharing mechanisms are available:

  • A child process created by the CreateProcess function can inherit a handle to a mutex object if thelpMutexAttributes parameter of CreateMutex enabled inheritance.
  • A process can specify the mutex-object handle in a call to the DuplicateHandle function to create a duplicate handle that can be used by another process.
  • A process can specify the name of a mutex object in a call to the OpenMutex orCreateMutex function.

Use the CloseHandle function to close the handle. The system closes the handle automatically when the process terminates. The mutex object is destroyed when its last handle has been closed.

Windows 95/98/Me: CreateMutexW is supported by the Microsoft Layer for Unicode. To use this, you must add certain files to your application, as outlined inMicrosoft Layer for Unicode on Windows 95/98/Me Systems.


WaitForSingleObject

等待互斥对象的使用权,如果第二个参数设置为INFINITE,则表示会持续等待下去,直到拥有所有权,才有权执行该函数下面的语句。一旦拥有了所有权,则会将互斥对象的的线程ID设置为当前使用的线程ID值。

The WaitForSingleObject function returns when one of the following occurs:

  • The specified object is in the signaled state.
  • The time-out interval elapses.

To enter an alertable wait state, use the WaitForSingleObjectEx function. To wait for multiple objects, use theWaitForMultipleObjects.

DWORD WaitForSingleObject(
  HANDLE hHandle,        // handle to object
  DWORD dwMilliseconds   // time-out interval
);
Parameters
hHandle
[in] Handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.

If this handle is closed while the wait is still pending, the function's behavior is undefined.

Windows NT/2000/XP: The handle must have SYNCHRONIZE access. For more information, seeStandard Access Rights.

dwMilliseconds
[in] Specifies the time-out interval, in milliseconds. The function returns if the interval elapses, even if the object's state is nonsignaled. If dwMilliseconds is zero, the function tests the object's state and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses.
Return Values

If the function succeeds, the return value indicates the event that caused the function to return. This value can be one of the following.

ValueMeaning
WAIT_ABANDONEDThe specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled.
WAIT_OBJECT_0The state of the specified object is signaled.
WAIT_TIMEOUTThe time-out interval elapsed, and the object's state is nonsignaled.

If the function fails, the return value is WAIT_FAILED. To get extended error information, callGetLastError.

Remarks

The WaitForSingleObject function checks the current state of the specified object. If the object's state is nonsignaled, the calling thread enters the wait state. It uses no processor time while waiting for the object state to become signaled or the time-out interval to elapse.

The function modifies the state of some types of synchronization objects. Modification occurs only for the object whose signaled state caused the function to return. For example, the count of a semaphore object is decreased by one.

The WaitForSingleObject function can wait for the following objects:

  • Change notification
  • Console input
  • Event
  • Job
  • Mutex
  • Process
  • Semaphore
  • Thread
  • Waitable timer

Use caution when calling the wait functions and code that directly or indirectly creates windows. If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. A thread that uses a wait function with no time-out interval may cause the system to become deadlocked. Two examples of code that indirectly creates windows are DDE and COMCoInitialize. Therefore, if you have a thread that creates windows, useMsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject.

For more information, see Using Mutex Objects.


ReleaseMutex

The ReleaseMutex function releases ownership of the specified mutex object.

BOOL ReleaseMutex(
  HANDLE hMutex   // handle to mutex
);
Parameters
hMutex
[in] Handle to the mutex object. The CreateMutex or OpenMutex function returns this handle.
Return Values

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, callGetLastError.

Remarks

The ReleaseMutex function fails if the calling thread does not own the mutex object.

A thread gets ownership of a mutex by specifying a handle to the mutex in one of thewait functions. The thread that creates a mutex object can also get immediate ownership without using one of the wait functions. When the owning thread no longer needs to own the mutex object, it calls theReleaseMutex function.

While a thread has ownership of a mutex, it can specify the same mutex in additional wait-function calls without blocking its execution. This prevents a thread from deadlocking itself while waiting for a mutex that it already owns. However, to release its ownership, the thread must call ReleaseMutex once for each time that the mutex satisfied a wait. 


可以将互斥对象想象成一把钥匙,CreateMutex创建了这把钥匙,WaitForSingleObject等待这把钥匙去访问一个公共的资源,比如一个房间,如果拥有了钥匙,则这个房间的所有权就属于这个进程了,别人是进不去这个房间的,直到进程将这个房间的钥匙归还掉,即ReleaseMutex。

eg:

#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
);

DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
);
int index=0;
int tickets=100;
HANDLE hMutex;
void main()
{
	HANDLE hThread1;
	HANDLE hThread2;
	hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
	hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	/*while(index++<1000)
		cout<<"main thread is running"<<endl;*/
	//hMutex=CreateMutex(NULL,TRUE,NULL);
	hMutex=CreateMutex(NULL,TRUE,"tickets");//因为TRUE,该线程(主线程)获得互斥对象的所有权,计数器+1。
	if(hMutex)
	{
		if(ERROR_ALREADY_EXISTS==GetLastError())
		{
			cout<<"only instance can run!"<<endl;
			return;
		}
	}
	WaitForSingleObject(hMutex,INFINITE);//等待到互斥对象所有权后,计数器再+1,此时计时器=2.
	ReleaseMutex(hMutex);//释放互斥对象所有权,此时计时器2-1=1.
	ReleaseMutex(hMutex);//此时计时器1-1=0.
	Sleep(4000);//暂停线程的执行,放弃互斥对象所有权。
//	Sleep(10);
}

DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
)
{
	/*while(index++<1000)
		cout<<"thread1 is running"<<endl;*/
	
	while(TRUE)
	{
		//ReleaseMutex(hMutex);
		WaitForSingleObject(hMutex,INFINITE);
		if(tickets>0)
		{
			Sleep(1);
			cout<<"thread1 sell ticket : "<<tickets--<<endl;
		}
		else
			break;
		ReleaseMutex(hMutex);
	}

	WaitForSingleObject(hMutex,INFINITE);
	cout<<"thread1 is running"<<endl;
	return 0;
}

DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
)
{
	
	while(TRUE)
	{
		//ReleaseMutex(hMutex);
		WaitForSingleObject(hMutex,INFINITE);
		if(tickets>0)
		{
			Sleep(1);
			cout<<"thread2 sell ticket : "<<tickets--<<endl;
		}
		else
			break;
		ReleaseMutex(hMutex);
	}
	WaitForSingleObject(hMutex,INFINITE);
	cout<<"thread2 is running"<<endl;
	return 0;
}


孙鑫老师深入详解VC++笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值