1、多线程概述
1.1 概述
相对进程而言,线程是一个更加接近于执行体的概念,他可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。桶多进程一样,多线程程序并不能真正提高程序的运行速度。在实际应用中,多线程通常仅仅是为了方便程序设计,具体地说,通常是用于有阻塞调用的场合,比如io的read;socket的recv等,以提高相应速度。
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程恰恰相反,资源的管理方便和保护方面比较安全;从可移植性来说,多进程的可移植性要好一些。
1.2 线程创建的Linux实现
Linux的线程是通过用户级的函数库实现的,内核提供的是创建进程的接口do_fork()。内核提供了两个系统调用_clone()和fork(),最终都用不同的参数调用do_fork()。当然,要想实现线程,没有核心对多进程共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间),CLONE_FS(共享文件系统信息),CLONE_FILES(共享文件描述符表),CLONE_SIGHAND(共享信号句柄表),CLONE_PID(共享进程ID)。
当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了这些属性来调用_clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由_clone()传入。
按照通俗的话说,linux线程就是轻量级的进程。以下以POSIX(标准)的线程标准pthread接口讲解。
2、线程的创建
pthread
是一个SYSTEM V
标准的线程编程接口(POSIX标准),其函数原型为:
#include<pthread.h> int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
thread是传出参数,保存新线程的标识;
attr是一个结构体指针,结构中额元素分别指定新线程的运行属性,各成员属性为:_detachstate,标识新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出是自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行最后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置),则不能够再恢复到PTHREAD_CREATE_JOINABLE状态;_shcedpolicy表示新线程的调度策略,主要包括SCHED_OTHER(正常,非实时)、SCHED_RR(实时,轮转法)和SCHED_FIFO(实时,先入先出)三种,缺省为SCHED_OTHER(正常,非实时),后两种调度策略仅对超级用户有效。运行时可以通过pthread_setschedparam()来改变;_schedparam,一个sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(SCHED_RR或者SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0;
_inheritsched,有两种值可以供选择:PTHREAD_EXPLICIT_SCHED和PATH_INHERIT_SCHED,前者表示新进程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为:PTHREAD_EXPLICIT_SCHED;_scope表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统内中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前Linux仅实现了PTHREAD_SCOPE_SYSTEM一值。
attr可以先用pthread_attr_init等函数设置各成员的值,但通常传入为NULL即可
start_routine是一个函数指针,指向新线程的入口点函数,线程入口点函数带有一个void*的参数,有pthread_create的第4个参数传入。
arg用于传递给第3个参数指向的入口点函数。
//示例 #inlcude<stdio.h> #include<unistd.h> #include<pthread.h> void *THreadFunc(void *pArg) { printf("Hi, I'm a child thread, arg is: %d\n", int(pArg)); return 0; } int main() { pthread_t thdID; pthread_create(&thdID,NULL,ThreadFunc,(void*)123); printf("Hi, I'm main thread, child thread id is :%x\n", thdID); sleep(1); return 0; }
3、线程的终止
3.1 线程的正常终止
线程从入口点函数自然返回,或者主动调用pthrad_exit()函数,都可以让线程正常终止。
线程从入口点函数自然返回是,函数返回值可以被其他线程用pthrad_join获取。
pthread_join的原型为:
#include<pthread.h> int pthread_join(pthread_t th, void ** thread_return);
该函数是一个阻塞函数,一直等到参数th指定的线程返回;ptread_return是一个传出参数,接收线程函数的返回值。该函数还有一个非常重要的作用,那就是如果th线程类型并不是自动清理资源类型的,则th线程退出后,线程本身的资源必须通过其他线程动用pthread_join来清除,这相当于多进程程序中的waitpid。
如果线程通过调用pthread_exit()终止,则pthrad_exit()中的参数相当于自然返回值,照样可以被其他线程用pthrad_join获取。
//示例 #include<stdio.h> #include<unistd.h> #include<pthread.h> void *ThreadFunc(void* pArg) { int& iArg = (int&)(pArg); sleep(iArg); if(iArg<3) return (void*)(iArg*2); else pthread_exit((void*)(iArg*2)); } int main() { pthread_t thdID; int iRet = 0; pthead_create(&thdID, NULL, ThreadFunc, (void*)2); puts("begin to wait.."); pthread_join(thdID, (void**)&iRet); printf("The first child thread ret is :%d\n", iRet); pthead_create(&thdID, NULL, ThreadFunc, (void*)3); puts("begin to wait.."); pthread_join(thdID, (void**)&iRet); printf("The second child thread ret is :%d\n", iRet); return 0; }
windows下的单个线程等待函数为WaitForSingleObject()
,其函数原型为:
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); //MSDN地址 //https://docs.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject?f1url=%3FappId%3DDev15IDEF1%26l%3DZH-CN%26k%3Dk(SYNCHAPI%252FWaitForSingleObject);k(WaitForSingleObject);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtru
//windows线程等待示例使用_beginthread #include<Windows.h> #include<process.h> #include<iostream> using namespace std; //回调函数 void theProc(void* p) { int *px = (int*)p; Sleep(100*3); *px *= 2; } int main() { int x = 3; uintptr_t h = _beginthread(theProc, 0,&x); cout << "Begin to wait.." << endl; int n = WaitForSingleObject((HANDLE)h, -1); cout << "x = " << x <<endl; return 0; }
//windows线程等待示例使用CreateThead #include<Windows.h> #include<process.h> #include<iostream> using namespace std; //回调函数 DWORD WINAPI theProc(LPVOID p) { int *px = (int*)p; Sleep(1000*3); *px *= 2; return 0; } int main() { int x = 3; HANDLE h = CreateThread(NULL, 0, theProc, &x, 0, &id); cout << "Begin to wait.." << endl; int n = WaitForSingleObject(h, -1); cout << "x = " << x <<endl; return 0; }
windows下的多个线程等待函数为WaitForMultipleObjects()
,其函数原型为:
DWORD WaitForMultipleObjects( DWORD nCount, const HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );
The WaitForMultipleObjects (可以等待的目标)function can specify handles of any of the following object types in the lpHandles array:
Change notification
Console input
Event //事件
Memory resource notification
Mutex //互斥
Process //进程
Semaphore //信号量
Thread //线程
Waitable timer
Windows等待进程
#include<Windows.h> int main() { //UINT n = winExec("mspaint.exe", SW_SHOW);//不能使用WaitForSingleObject进行等待 //HINSTANCE h = ShellExecute(NULL, NULL, "mapaint.exe", NULL, NULL, SW_SHOW);//不能使用WaitForSingleObject进行等待 STARTUPINFO si = {sizeof(si)}; PROCESS_INFOMATION pi; BOOL b = CreateProcess(NULL, "mspaint.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);//可以使用WaitForSingleObject进行等待 return 0; }
3.2 线程取消
线程也可以被其他线程杀掉,在Linux中的说法是一个线程被另一个线程取消(cancel)。线程取消的方法是一个线程向目标线程法cancel信号,但是如何处理cancel信号则由目标线程自己决定,目标线程或者忽略、或者立即终止、或者继续运行至cancelation-point(取消点)后终止。
**取消点:**根据POSIX标准,pthread_join、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是cancelation-point,而其他phread函数都不会引起Cancelation动作。但是pthread_cancel手册声称,由于linux线程库与C库结合的不好,因而目前C库函数都不是cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为cancelation-point的系统调用前后调用pthread_testcancel(),从而达到PPOSIX标准所要求的目标。如下述代码段:
pthread_testcancel(); retcode=read(fd,buff, length); pthread_testcancel();
从实际测试来看,至少有些C函数库的阻塞函数是取消点,如read()、getchar()等,而sleep()函数不管线程是否设置了pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)都起到取消点的作用。总之,线程的取消一方面是一个线程强行杀死另外一个线程,从程序设计角度并不是一种好的风格,另一方面目前linux对这方面的支持并不完善,所以在设计应用中应该尽量避免。
3.3 线程终止清理函数
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利释放掉自己占用的内存,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远锁定状态得不到释放。外界取消操作是不可能遇见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对于自动释放资源–从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API的定义如下:
void pthread_cleanup_push(void(*routine)(void*), void*arg); void pthread_cleanup_pop(int execute);
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,再执行该函数链时按照哦亚展的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行难该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,下面是pthread.h中的宏定义:
#define ptread_cleanup_push(routine, arg) { struct _pthread_cleanup_buff _buffer; _pthread_cleanup_push(&_buffer, (routine), (arg)); #define phtread_cleanup_pop(execute) _pthread_cleanup_pop(&buffer, (execute)); }
可见pthread_cleanup_pop
的参数execute如果为0,则按栈的顺序注销掉一个原来注册的清理函数,否则不清除。
#include<stdio.h> #include<unistd.h> #include<pthread.h> void CleanFunc(void *pArg) { printf("CleanFUnc(%d)\n", (int&)pArg); } void *ThreadFunc(void *pArg) { pthread_cleanup_push(CLeanFunc, (void*)1); pthread_cleanup_push(CLeanFunc, (void*)2); sleep(2); pthread_cleanup_pop(1); pthread_cleanup_pop(1); return 0; } int main() { pthread_t thdId; //启动线程 pthread_create(&thdId, NULL, ThreadFunc, (void*)2); pthread_join(thdId, NULL); return 0; }
4、线程的同步与互斥
4.1 线程的互斥
在poxis thread中定义了一套专门用于线程互斥的mutex函数。
4.1.1 创建和销毁
有两种方法创建互斥锁,静态方式和动态方式;POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER
来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
在linuxthread实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER是一个结构常量。
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr *mutexattr);
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在linux中,互斥锁并不占用任何资源,因此linuxThreads中的pthread_mutex_destroy
除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
4.1.2 互斥锁属性
互斥锁属性结构体的定义为:
typedef struct { int _mutexkind }pthread_mutexattr_t;
互斥锁的属性在创建所的时候指定,在linuxThreads实现中仅有一个锁类型_mutexkind,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同,有以下三个值可供选择:
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁(或快速锁)。当一个线程加锁之后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
4.1.3 *
EPERM:对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表名并没有这种限制,这个不同目前还没有得到解释。在同一个进程中的线程,如果加锁后没有解锁,责任和其他线程都无法再获得锁。
pthread_mutex_unlock根据不同的锁类型,实现不同的行为:对于快速锁,pthread_mutex_unlock解除锁定;对于递归锁,pthread_mutex_unlock使锁上的 引用次数-1;对于检错锁,如果锁是当前线程锁定的,则解除锁定,否则什么都不做。
pthread_mutex_trylock和pthread_mutex_lock的语义类似,不同的是在锁已经被占用时返回EBUSY而不是挂起等待。
4.1.4 其他
POSIX线程锁机制的linux实现都不是取消点,因此延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁之后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。这个锁禁止同时也不是异步信号安全(即不可重入)的,也就是说,不应该在信号处理函数中使用互斥锁,否则容易造成死锁。
//线程锁互斥--示例 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> #include<errno.h> pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; //回调函数 void * theProc(void* p) { while(true) { pthread_mutex_lock(&g_mutex); sleep(2); printf("p=%d\n", (int&)p); pthread_mutex_unlock(&g_mutex); sleep(1); } } int main() { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutex_init(&g_mutex, &attr);//初始化mutex句柄 int i = 0; while(i < 5) { pthread_t id = 0; pthread_create(&id, NULL, theProc, (void*&)++i); } getchar(); return 0; }
4.2 条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁集合在一起。
4.2.1 条件变量的创建和注销
创建方式分为静态创建和动态创建两种,静态方式使用PTHREAD_COND_INITIALIZER
常量,动态方式使用pthread_cond_init()
函数,创建语句如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态方式 int pthread_cond_init(pthread_cond_t*cond, pthread_condattr*cond_attr);//动态方式
注销一个调节键变量需要调用pthread_cond_destroy()
函数,当条件变量上没有等待线程时可以注销此条件变量,否则返回EBUSY
。
int pthread_cond_destroy(pthread_cond_t*cond);//注销函数
4.2.2 等待和激发
等待条件有两种方式:无条件等待和计时等待。
int pthread_cond_wait(pthread_cond_t*cond, pthread_mutex_t*mutex);//无条件等待 int pthread_cond_timedwait(pthread_cond_t*cond, pthread_mutex_t*mutex,const struct timespec *abstime);//计时等待
其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与系统调用想同意义的绝对时间形式出现,0表示格林尼治时间。这些等待方式都必须和一个互斥锁进行配合,以防止多个线程同时请求pthread_cond_wait()的竞争条件。mutex互斥锁必须是普通锁,且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),在更新条件等待队列之前,mutex保持锁定状态,并在线程挂起进入等待前进行解锁。在条件满足离开pthread_cond_wait()之前,mutex将被重新加锁。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按照入队顺序激活队列头的进程;pthread_cond_broadcast()会激活所有等待的线程。
4.2.3 其他
pthread_cond_wait()
和pthread_cond_timedwait
都被实现为取消点,在离开时将重新锁定mutex,然后执行取消操作。也就是说上述等待被取消之后,mutex仍然保持锁定状态,因此需要定义退出回调函数来为其解锁。
//条件变量使用示例 #include<stdio.h> #include<pthread.h> #include<unistd.h> pthread_cond_t cond; pthread_mutex_t mutex; void* theProc(void *p) { int& n = (int&)p; pthread_cond_wait(&cond, &mutex);//条件变量等待 puts("The thread begin to work!"); } int main() { pthread_cond_init(&cond, NULL);//条件变量初始化 pthread_t id; pthread_create(&id, NULL, theProc, (void*)3); char s[32]; fgets(s,sizeof(s),stdin); pthread_cond_signal(&cond); sleep(1); return 0; }
//条件变量使用示例2 #include<stdio.h> #include<pthread.h> #include<unistd.h> pthread_cond_t cond; pthread_mutex_t mutex; void* theProc(void *p) { sleep(5); int& n = (int&)p; pthread_cond_signal(&cond);//条件变量等待 puts("Work finish!"); } int main() { pthread_cond_init(&cond, NULL);//条件变量初始化 pthread_t id; pthread_create(&id, NULL, theProc, (void*)3); pthread_cond_wait(&cond,&mutex); sleep(1); return 0; }
//windows下条件变量示例 #include<windows.h> #include<process.h> #include<stdio.h> HANDLE g_hEvent; bool g_bRun = true; void theProc(void *p) { while (true) { WaitForSingleObject(g_hEvent, -1); if (!g_bRun)break; puts("ok, i begin to work!"); } puts("Over!"); } int main() { g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); _beginthread(theProc, 0, NULL); char s[32]; while (g_bRun) { gets_s(s, sizeof(s)); if (*s=='#') g_bRun=false; SetEvent(g_hEvent); } Sleep(2000); return 0; }
4.3 信号灯-semaphore
信号灯和互斥锁的条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着资源不可用。如果说后两种同步方式侧重于“等待”操作,即资源不可用的话,信号灯机制则测重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作也意味着更多的开销。
信号灯的应用除了灯亮/灯灭这种二元等以外,也可以采用大于1的灯数,以表示资源数大于1,这是可以称之为多元灯。
4.3.1 创建和注销
POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但linuxThread的实现仅有无名信号灯,同时有名信号灯处理总是可以用于多进程之间以外,在使用上与无名信号灯没有很大的去比恩,因此下面就无名信号灯进行讨论。
int sem_init(sem_t *sem, int pshared, unsigned int value)这是创建信号灯的API,其中value为信号灯的初始值,pshared表示是否为多线程共享而不仅仅是用于一个进程。linuxThread没有实现多进程共享信号灯,因此所有非0值的pshareed输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于以下点灯,灭灯操作。
int sem_destroy(sem_t *sem)被注销的信号灯sem要求已经没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,linuxThread的信号灯注销函数不做其他动作。
4.3.2 点灯和灭灯
int sem_post(sem_t *sem)点灯操作将信号灯值原子地+1,表示增加一个可以访问的资源。
int sem_wait(sem_t *sem)为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地-1,并返回。
int sem_trywait(sem_t *sem)为sem_wait()的非阻塞版本,如果信号灯计数大于0,则原子地-1并返回0,否则立即返回-1,errno置为EAGAIN。
4.3.3 获取灯值
int sem_getvalue(sem_t *sem, int *sval)
读取sem
中的灯计数,存于*sval
中,并返回0。
4.3.4 其他
sem_wait()
被实现为取消点,而且在支持原子“比较且交换”指令的体系结构上,sem_post()
是唯一能用于异步信号处理函数的POSIX异步安全的API。
//信号灯可以同时控制多个线程,与mutex形成对比,mutex示例如下 #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<pthread.h> #include<semaphore.h> sem_t bin_sem; pthread_mutex_t mutex; void* theProc(void *arg) { int &n = (int&)arg; pthread_mutex_lock(&mutex); int i = 0; while(i<5) { sleep(2); printf("n=%d\n", n); ++i; } pthread_mutex_unlock(&mutex); } int g_nCount = 0; int main() { pthread_t id; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutex_init(&mutex, &attr);//初始化mutex句柄 while(++g_nCount<5) { pthread_create(&id, NULL, theProc, (void*)g_nCount); } getchar(); }
//semaphore示例 //信号灯可以同时控制多个线程,与mutex形成对比,mutex示例如下 #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<pthread.h> #include<semaphore.h> sem_t bin_sem; pthread_mutex_t mutex; int g_nCount = 0; pthread_cond_t cond; void* theProc(void *arg) { int &n = (int&)arg; sem_wait(&bin_sem); int i = 0; while(i<5) { sleep(1); printf("n=%d\n", n); ++i; } sem_post(&bin_sem); if(--g_nCount==1) pthread_cond_signal(&cond); printf("g_nCount = %d\n", g_nCount); } int main() { sem_init(&bin_sem, 0, 3); pthread_t id; while(++g_nCount<10) { pthread_create(&id, NULL, theProc, (void*)g_nCount); } pthread_cond_wait(&cond,&mutex); return 0; }
//windows信号量示例 #include<windows.h> #include<process.h> #include<stdio.h> HANDLE g_hSem; HANDLE g_hEvent; int g_nCount = 0; void theProc(void *p) { int n = (int)p; WaitForSingleObject(g_hSem, -1); int i = 0; while (++i < 5) { printf("%d\n", n); Sleep(2000); } LONG nCount = 0; ReleaseSemaphore(g_hSem, 1, &nCount); if (--g_nCount == 1) SetEvent(g_hEvent); //puts("Release one object!"); printf("Release one object : %d\n", n); } int main() { g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); g_hSem = CreateSemaphore(NULL, 3, 3, NULL); while (++g_nCount < 10) { _beginthread(theProc, 0, (void*)g_nCount); } WaitForSingleObject(g_hEvent,-1); return 0; }
*、测试Linux线程
测试Linux多线程(thread)原理:
#include<iostream> #include <pthread.h> #include <unistd.h> using namespace std; void* theProc(void *p) { int& n = (int&)p; while(true) { cout << n << endl; sleep(1); } } int main() { int n = 1; while(n>0) { cin >> n; pthread_t id; pthread_create(&id,NULL, theProc,(void*)n);//用于创建线程的函数 } return 0; }
测试Linux多线程(thread)等待,线程堵塞函数pthread_join
:
#include<iostream> #include <pthread.h> #include <unistd.h> using namespace std; void* theProc(void *p) { int& n = (int&)p; int i = 0; while(i<5) { cout << n << endl; sleep(1); ++i; } return p; } int main() { int n = 1; cin >> n; pthread_t id; pthread_create(&id,NULL, theProc,(void*)n);//用于创建线程的函数 cout << "waiting..." << endl; void * p = 0; pthread_join(id,&p); //线程/或者进程等待,等待的过程叫做同步 cout << "waiting ended" << endl; return 0; }
4.4 消息发送
//msgrecv编写,发送端 #include <stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> int main() { int iMsgQueId; iMsgQueId = msgget(6611, 0666|IPC_CREAT); char s[128]; while(*s != '#') { fgets(s,sizeof(s),stdin); if(msgsnd(iMsgQueId,s,sizeof(s),0)==-1) { perror("msgsnd fail"); return -2; } } return 0; }
//msgrecv编写,发送端 #include <stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> int main() { int iMsgQueId; iMsgQueId = msgget(6611, 0666|IPC_CREAT); char s[128]; while(*s != '#') { if(msgrcv(iMsgQueId, s,sizeof(s), 0, 0) == -1) { perror("msgsnd fail"); return -2; } printf("Get:%s", s); } return 0; }
测试Linux多线程(thread)通讯`:
#include<iostream> #include <pthread.h> #include <unistd.h> using namespace std; //最简单的线程间通讯 bool g_bRun = true; void* theProc(void *p) { int& n = (int&) p; while(g_bRun) { cout << n << endl; //sleep(1); } return p; } int main() { int n = 1; cin >> n; pthread_t id; pthread_create(&id,NULL, theProc,(void*)n);//用于创建线程的函数 cout << "waiting..." << endl; g_bRun = false; //将线程通讯标记标记为false,让所有线程都进行退出 cout << "waiting ended" << endl; return 0; }