Linux 线程大总结

简介: Linux 线程大总结

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;
}


相关文章
|
17天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
49 17
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
60 26
|
7月前
|
算法 Unix Linux
linux线程调度策略
linux线程调度策略
134 0
|
5月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
76 0
Linux C/C++之线程基础
|
5月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
7月前
|
存储 设计模式 NoSQL
Linux线程详解
Linux线程详解
|
7月前
|
缓存 Linux C语言
Linux线程是如何创建的
【8月更文挑战第5天】线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。
|
7月前
|
负载均衡 Linux 调度
在Linux中,进程和线程有何作用?
在Linux中,进程和线程有何作用?
|
7月前
|
缓存 Linux C语言
Linux中线程是如何创建的
【8月更文挑战第15天】线程并非纯内核机制,由内核态与用户态共同实现。