Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
CODE PROJECT For Those Who Code
  • Home
  • Articles
  • FAQ
Community
  1. Home
  2. General Programming
  3. C / C++ / MFC
  4. Help on Multi-Threaded applicaiton [modified]

Help on Multi-Threaded applicaiton [modified]

Scheduled Pinned Locked Moved C / C++ / MFC
questionperformancehelpannouncement
41 Posts 4 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Mark Salsbery

    Kiran Satish wrote:

    I have to say that the loop function runs on a TIMER which has 5msec timeout value

    What kind of timer?

    Mark Salsbery Microsoft MVP - Visual C++ :java:

    K Offline
    K Offline
    Kiran Satish
    wrote on last edited by
    #9

    Its the default MFC Timer that we initialize using CWnd::SetTimer function, but the camera grabbing runs in a different thread.

    PKNT

    M 1 Reply Last reply
    0
    • K Kiran Satish

      Its the default MFC Timer that we initialize using CWnd::SetTimer function, but the camera grabbing runs in a different thread.

      PKNT

      M Offline
      M Offline
      Mark Salsbery
      wrote on last edited by
      #10

      Kiran Satish wrote:

      ts the default MFC Timer that we initialize using CWnd::SetTimer function

      Not good. I recommend multimedia timers (after all, this is what they're for!). IME, on XP+ they are pretty precise to 1ms :)

      Kiran Satish wrote:

      but the camera grabbing runs in a different thread

      Are you responsible for grabbing frames at the interval you want or does the hardware/driver signal you somehow when a frame is available? IME, the ideal situation is when the driver can signal an event that wakes a thread for each frame. If that's not possible and you have to grab frames at a given interval, the next best performance is with multimedia timers. I personally wouldn't use Windows timers except for situations where timer resolution doesn't matter at all. Mark

      Mark Salsbery Microsoft MVP - Visual C++ :java:

      K 1 Reply Last reply
      0
      • M Mark Salsbery

        Kiran Satish wrote:

        ts the default MFC Timer that we initialize using CWnd::SetTimer function

        Not good. I recommend multimedia timers (after all, this is what they're for!). IME, on XP+ they are pretty precise to 1ms :)

        Kiran Satish wrote:

        but the camera grabbing runs in a different thread

        Are you responsible for grabbing frames at the interval you want or does the hardware/driver signal you somehow when a frame is available? IME, the ideal situation is when the driver can signal an event that wakes a thread for each frame. If that's not possible and you have to grab frames at a given interval, the next best performance is with multimedia timers. I personally wouldn't use Windows timers except for situations where timer resolution doesn't matter at all. Mark

        Mark Salsbery Microsoft MVP - Visual C++ :java:

        K Offline
        K Offline
        Kiran Satish
        wrote on last edited by
        #11

        I never used those multimedia timers, maybe its time for me to use them :) . Coming to camera grabbing, we use a command from the frame grabber's library and whenever you call that command, it grabs a frame with a set predefined exposure time which, in our case is 20ms. So unless you call that command it wont capture a frame.

        PKNT

        M 2 Replies Last reply
        0
        • K Kiran Satish

          I never used those multimedia timers, maybe its time for me to use them :) . Coming to camera grabbing, we use a command from the frame grabber's library and whenever you call that command, it grabs a frame with a set predefined exposure time which, in our case is 20ms. So unless you call that command it wont capture a frame.

          PKNT

          M Offline
          M Offline
          Mark Salsbery
          wrote on last edited by
          #12

          In that case, it seems like you don't need a timer. The grabber thread can loop grabbing frames - the exposure time will throttle the loop to 50 fps. After each frame is grabbed, set an event that wakes the processing thread. The processing thread can loop waiting on the grabber event. When the event is signaled, it can process the frame and wait again. If your processing time is ~10ms your frame-data critical section should never have to wait/block. Ideally, you'd want to eliminate the need for the critical section altogether if possible, even if you have to copy the frame data to another buffer on the processing thread. Mark

          Mark Salsbery Microsoft MVP - Visual C++ :java:

          1 Reply Last reply
          0
          • K Kiran Satish

            I never used those multimedia timers, maybe its time for me to use them :) . Coming to camera grabbing, we use a command from the frame grabber's library and whenever you call that command, it grabs a frame with a set predefined exposure time which, in our case is 20ms. So unless you call that command it wont capture a frame.

            PKNT

            M Offline
            M Offline
            Mark Salsbery
            wrote on last edited by
            #13

            Here's what I meant in the last post:

            // Camera Thread
            While capturing
            {
            Capture frame (20ms exposure)
            Add frame data to shared buffer
            Set frame-grabbed event
            }

            // Processing Thread
            While processing
            {
            Wait on frame-grabbed event
            Copy data from shared buffer
            Process the data
            }

            You still need to synchronize access to the shared buffer. Only lock long enough to copy data to and from the buffer, then release the lock immediately. That's all based on ideal situation where processing time is always ~10ms. In reality, your threads can (and will be) interrupted by other threads in the system. If it's critical to process every frame, then change the shared buffer to a FIFO queue of some kind. The grabber thread can queue each frame and the processing thread can dequeue frames. If the queue holds pointers to frame buffers, then accessing the queue is fast, which makes lock time shorter. That's pretty much how I handle audio capture, where I want every sample regardless of thread timing fluctuations. Does that make sense? :) Mark

            Mark Salsbery Microsoft MVP - Visual C++ :java:

            K 1 Reply Last reply
            0
            • M Mark Salsbery

              Here's what I meant in the last post:

              // Camera Thread
              While capturing
              {
              Capture frame (20ms exposure)
              Add frame data to shared buffer
              Set frame-grabbed event
              }

              // Processing Thread
              While processing
              {
              Wait on frame-grabbed event
              Copy data from shared buffer
              Process the data
              }

              You still need to synchronize access to the shared buffer. Only lock long enough to copy data to and from the buffer, then release the lock immediately. That's all based on ideal situation where processing time is always ~10ms. In reality, your threads can (and will be) interrupted by other threads in the system. If it's critical to process every frame, then change the shared buffer to a FIFO queue of some kind. The grabber thread can queue each frame and the processing thread can dequeue frames. If the queue holds pointers to frame buffers, then accessing the queue is fast, which makes lock time shorter. That's pretty much how I handle audio capture, where I want every sample regardless of thread timing fluctuations. Does that make sense? :) Mark

              Mark Salsbery Microsoft MVP - Visual C++ :java:

              K Offline
              K Offline
              Kiran Satish
              wrote on last edited by
              #14

              Yeh, it makes sense, thanks a lot. Actually thats what I like to implement , but I am not thinking of implementing locking of shared buffer earlier. But before implementing this, I would like to time exactly how much time each function in the processing thread is taking in worst case. Based on those timings, I would implement queuing on the captured frames. I have to change a lot of code thats currently been implemented. Since, this is not our lab's work, I might be working on and off on this. So, will keep here posted on how it goes :) .

              PKNT

              M 1 Reply Last reply
              0
              • K Kiran Satish

                Yeh, it makes sense, thanks a lot. Actually thats what I like to implement , but I am not thinking of implementing locking of shared buffer earlier. But before implementing this, I would like to time exactly how much time each function in the processing thread is taking in worst case. Based on those timings, I would implement queuing on the captured frames. I have to change a lot of code thats currently been implemented. Since, this is not our lab's work, I might be working on and off on this. So, will keep here posted on how it goes :) .

                PKNT

                M Offline
                M Offline
                Mark Salsbery
                wrote on last edited by
                #15

                Cool good luck! Keep in mind: Locking is bad - eliminate it if possible - only lock for as long as necessary, and there's lots of other threads running in the system to mess with your timing :) Mark

                Mark Salsbery Microsoft MVP - Visual C++ :java:

                K 2 Replies Last reply
                0
                • M Mark Salsbery

                  Cool good luck! Keep in mind: Locking is bad - eliminate it if possible - only lock for as long as necessary, and there's lots of other threads running in the system to mess with your timing :) Mark

                  Mark Salsbery Microsoft MVP - Visual C++ :java:

                  K Offline
                  K Offline
                  Kiran Satish
                  wrote on last edited by
                  #16

                  I have a question, I tried to implement the same thing in my application (with some difference, in our application we can not grab frame while processing the current image as we have to update other hardware before grabbing next frame). Here is what I tried to do, I have a camera thread and my loop thread. The loop thread waits for the Camera thread to get a new frame and once the camera gets new frame, it waits for the loop thread until it process and signals it to get next frame. Thats the basic idea. After doing the current image analysis and changing the hardware status based upon current image analysis results, I spend around 6-8ms to update some displays and do some logs, so I thought I can signal the camera meanwhile to get new frame. But I am stuck with deadlocks with signal flags between threads.

                  Loop thread
                  do{
                  if (newframe){
                  do analysis on the frame
                  signal = true;//signal the camera to get new frame
                  update displays and do logs for the current frame
                  }
                  }while(looprunning);

                  camera thread
                  do{
                  if (signal){
                  newframe = false;
                  get new frame - takes 20ms
                  newframe = true;
                  signal = false;
                  }
                  }while(camrunning);

                  I can see an obvious deadlock, how can I get around this problem :confused: ? -thanks

                  PKNT

                  M 1 Reply Last reply
                  0
                  • K Kiran Satish

                    I have a question, I tried to implement the same thing in my application (with some difference, in our application we can not grab frame while processing the current image as we have to update other hardware before grabbing next frame). Here is what I tried to do, I have a camera thread and my loop thread. The loop thread waits for the Camera thread to get a new frame and once the camera gets new frame, it waits for the loop thread until it process and signals it to get next frame. Thats the basic idea. After doing the current image analysis and changing the hardware status based upon current image analysis results, I spend around 6-8ms to update some displays and do some logs, so I thought I can signal the camera meanwhile to get new frame. But I am stuck with deadlocks with signal flags between threads.

                    Loop thread
                    do{
                    if (newframe){
                    do analysis on the frame
                    signal = true;//signal the camera to get new frame
                    update displays and do logs for the current frame
                    }
                    }while(looprunning);

                    camera thread
                    do{
                    if (signal){
                    newframe = false;
                    get new frame - takes 20ms
                    newframe = true;
                    signal = false;
                    }
                    }while(camrunning);

                    I can see an obvious deadlock, how can I get around this problem :confused: ? -thanks

                    PKNT

                    M Offline
                    M Offline
                    Mark Salsbery
                    wrote on last edited by
                    #17

                    Flags? no no no :) There's handy synchronization objects for that, something like:

                    autoreset events:
                    newframeevent
                    signalevent

                    Loop thread
                    do
                    {
                    waitForSingleObject(newframeevent)
                    do analysis on the frame
                    SetEvent(signalevent) //signal the camera to get new frame
                    update displays and do logs for the current frame
                    }
                    while(looprunning);

                    camera thread
                    do
                    {
                    waitForSingleObject(signalevent)
                    get new frame - takes 20ms
                    SetEvent(newframeevent)
                    }
                    while(camrunning);

                    The worst thing I see in the flags implementation is two threads modifying the signal flag. Mark

                    Mark Salsbery Microsoft MVP - Visual C++ :java:

                    K 2 Replies Last reply
                    0
                    • M Mark Salsbery

                      Flags? no no no :) There's handy synchronization objects for that, something like:

                      autoreset events:
                      newframeevent
                      signalevent

                      Loop thread
                      do
                      {
                      waitForSingleObject(newframeevent)
                      do analysis on the frame
                      SetEvent(signalevent) //signal the camera to get new frame
                      update displays and do logs for the current frame
                      }
                      while(looprunning);

                      camera thread
                      do
                      {
                      waitForSingleObject(signalevent)
                      get new frame - takes 20ms
                      SetEvent(newframeevent)
                      }
                      while(camrunning);

                      The worst thing I see in the flags implementation is two threads modifying the signal flag. Mark

                      Mark Salsbery Microsoft MVP - Visual C++ :java:

                      K Offline
                      K Offline
                      Kiran Satish
                      wrote on last edited by
                      #18

                      Thanks, I feel like a complete dumbo. I always use CEvent::WaitForSingleObject(,) in many threads so that they wont lock the CPU and never thought of using them here. Maybe I need to start thinking slowly ;P

                      PKNT

                      M 2 Replies Last reply
                      0
                      • K Kiran Satish

                        Thanks, I feel like a complete dumbo. I always use CEvent::WaitForSingleObject(,) in many threads so that they wont lock the CPU and never thought of using them here. Maybe I need to start thinking slowly ;P

                        PKNT

                        M Offline
                        M Offline
                        Mark Salsbery
                        wrote on last edited by
                        #19

                        :)

                        Kiran Satish wrote:

                        I always use CEvent::WaitForSingleObject(,) in many threads so that they wont lock the CPU

                        Yes!. It also throttles your thread loops so they don't sit there spinning while waiting for the flag to be set, eating all your CPU for 1 processor (each). Mark

                        Mark Salsbery Microsoft MVP - Visual C++ :java:

                        1 Reply Last reply
                        0
                        • K Kiran Satish

                          Thanks, I feel like a complete dumbo. I always use CEvent::WaitForSingleObject(,) in many threads so that they wont lock the CPU and never thought of using them here. Maybe I need to start thinking slowly ;P

                          PKNT

                          M Offline
                          M Offline
                          Mark Salsbery
                          wrote on last edited by
                          #20

                          plus...you can add a terminate event if needed so the threads can be shutdown gracefully. Then you'd wait on multiple objects. Mark

                          Mark Salsbery Microsoft MVP - Visual C++ :java:

                          1 Reply Last reply
                          0
                          • M Mark Salsbery

                            Flags? no no no :) There's handy synchronization objects for that, something like:

                            autoreset events:
                            newframeevent
                            signalevent

                            Loop thread
                            do
                            {
                            waitForSingleObject(newframeevent)
                            do analysis on the frame
                            SetEvent(signalevent) //signal the camera to get new frame
                            update displays and do logs for the current frame
                            }
                            while(looprunning);

                            camera thread
                            do
                            {
                            waitForSingleObject(signalevent)
                            get new frame - takes 20ms
                            SetEvent(newframeevent)
                            }
                            while(camrunning);

                            The worst thing I see in the flags implementation is two threads modifying the signal flag. Mark

                            Mark Salsbery Microsoft MVP - Visual C++ :java:

                            K Offline
                            K Offline
                            Kiran Satish
                            wrote on last edited by
                            #21

                            I tried to implement this in simple way to test and rather than waiting for the event to occur, it runs continuously even I define the timeout value to be INIFINITE

                            getsnap()
                            {
                            start camera thread if not running
                            PulseEvent(getnewframe)
                            waitForSingleObject(newframeevent, INFINITE)
                            return;
                            }

                            camera thread
                            do
                            {
                            waitForSingleObject(getnewframe, INFINITE)
                            get new frame - takes 20ms
                            PulseEvent(newframeevent)
                            }while(camrunning);

                            Here the camera thread continues running even after taking a frame. I am sure 'm missing something here. Sorry for bothering :) . -thanks

                            PKNT

                            M 1 Reply Last reply
                            0
                            • K Kiran Satish

                              I tried to implement this in simple way to test and rather than waiting for the event to occur, it runs continuously even I define the timeout value to be INIFINITE

                              getsnap()
                              {
                              start camera thread if not running
                              PulseEvent(getnewframe)
                              waitForSingleObject(newframeevent, INFINITE)
                              return;
                              }

                              camera thread
                              do
                              {
                              waitForSingleObject(getnewframe, INFINITE)
                              get new frame - takes 20ms
                              PulseEvent(newframeevent)
                              }while(camrunning);

                              Here the camera thread continues running even after taking a frame. I am sure 'm missing something here. Sorry for bothering :) . -thanks

                              PKNT

                              M Offline
                              M Offline
                              Mark Salsbery
                              wrote on last edited by
                              #22

                              I would use (and recommend) SetEvent() on auto-reset events and not PulseEvent(): getnewframe = ::CreateEvent(NULL, FALSE, FALSE, NULL); newframeevent = ::CreateEvent(NULL, FALSE, FALSE, NULL); I can't remember why but something about PulseEvent() is flaky/unreliable if I recall correctly (besides the fact that a thread has to be already waiting on it for it to work, which is bad for this case)....I just know I never use it. Compare that to SetEvent() - "The state of an auto-reset event object remains signaled until a single waiting thread is released". That's the behavior you'd want. Mark

                              Mark Salsbery Microsoft MVP - Visual C++ :java:

                              K 1 Reply Last reply
                              0
                              • M Mark Salsbery

                                I would use (and recommend) SetEvent() on auto-reset events and not PulseEvent(): getnewframe = ::CreateEvent(NULL, FALSE, FALSE, NULL); newframeevent = ::CreateEvent(NULL, FALSE, FALSE, NULL); I can't remember why but something about PulseEvent() is flaky/unreliable if I recall correctly (besides the fact that a thread has to be already waiting on it for it to work, which is bad for this case)....I just know I never use it. Compare that to SetEvent() - "The state of an auto-reset event object remains signaled until a single waiting thread is released". That's the behavior you'd want. Mark

                                Mark Salsbery Microsoft MVP - Visual C++ :java:

                                K Offline
                                K Offline
                                Kiran Satish
                                wrote on last edited by
                                #23

                                I tired to implement using SetEvent, but it still deadlocks, as I dont see any processor activity at all and just a busy mouse icon on the application interface. Here is how I did it - I defined the event handles at the start of the application in your way rather than using CEvent objects like I did before

                                getnewframe = ::CreateEvent(NULL, FALSE, FALSE, NULL);
                                newframeevent = ::CreateEvent(NULL, FALSE, FALSE, NULL);

                                when the user clicks snapshot, I call a function (snapshot)

                                snapshot(exposure)
                                {
                                set camera exposure
                                start snapshotthread if not running
                                SetEvent(getnewframe)
                                ::WaitForSingleObject(waitfornewframe, INFINITE);
                                display the frame
                                }
                                .
                                .
                                .
                                camthread
                                {
                                do
                                {
                                ::WaitForSingleObject(getnewframe, INFINITE);
                                take a 20ms snapshot
                                SetEvent(waitfornewframe);
                                }while(camrunning)
                                }

                                I still dont get where I am doing wrong :confused:. I even tried using a while loop in snapshot function and using a 1ms timeout value for WaitForSingleObject and checking for WAIT_OBJECT_0.

                                PKNT

                                M 1 Reply Last reply
                                0
                                • K Kiran Satish

                                  I tired to implement using SetEvent, but it still deadlocks, as I dont see any processor activity at all and just a busy mouse icon on the application interface. Here is how I did it - I defined the event handles at the start of the application in your way rather than using CEvent objects like I did before

                                  getnewframe = ::CreateEvent(NULL, FALSE, FALSE, NULL);
                                  newframeevent = ::CreateEvent(NULL, FALSE, FALSE, NULL);

                                  when the user clicks snapshot, I call a function (snapshot)

                                  snapshot(exposure)
                                  {
                                  set camera exposure
                                  start snapshotthread if not running
                                  SetEvent(getnewframe)
                                  ::WaitForSingleObject(waitfornewframe, INFINITE);
                                  display the frame
                                  }
                                  .
                                  .
                                  .
                                  camthread
                                  {
                                  do
                                  {
                                  ::WaitForSingleObject(getnewframe, INFINITE);
                                  take a 20ms snapshot
                                  SetEvent(waitfornewframe);
                                  }while(camrunning)
                                  }

                                  I still dont get where I am doing wrong :confused:. I even tried using a while loop in snapshot function and using a 1ms timeout value for WaitForSingleObject and checking for WAIT_OBJECT_0.

                                  PKNT

                                  M Offline
                                  M Offline
                                  Mark Salsbery
                                  wrote on last edited by
                                  #24

                                  I'm not seeing a deadlock situation in the pseudo code... If you run it in the debugger and break, are both threads sitting in the Wait function? If so, why?

                                  Mark Salsbery Microsoft MVP - Visual C++ :java:

                                  K 1 Reply Last reply
                                  0
                                  • M Mark Salsbery

                                    I'm not seeing a deadlock situation in the pseudo code... If you run it in the debugger and break, are both threads sitting in the Wait function? If so, why?

                                    Mark Salsbery Microsoft MVP - Visual C++ :java:

                                    K Offline
                                    K Offline
                                    Kiran Satish
                                    wrote on last edited by
                                    #25

                                    Here is what happening in debug more

                                    snapshot(exposure)
                                    {
                                    set camera exposure
                                    start snapshotthread if not running
                                    SetEvent(getnewframe)
                                    ::WaitForSingleObject(waitfornewframe, INFINITE); -- set breakpoint(1)
                                    display the frame
                                    }
                                    .
                                    .
                                    .
                                    camthread
                                    {
                                    do
                                    {
                                    dummy variable -- set breakpoint(2)
                                    ::WaitForSingleObject(getnewframe, INFINITE);
                                    take a 20ms snapshot
                                    SetEvent(waitfornewframe);
                                    }while(camrunning)
                                    }

                                    when I hit snapshot button, the program stops at breakpoint(1) and then goes to breakpoint(2) and thats it from there it does nothing while the C++ interface shows as the program is in [run] mode. From the definitions of SetEvent and WaitForSingleObject I too dont see any thing wrong here. Do they need to be in actual two different threads, I dont think thats the case, interesting!

                                    PKNT

                                    M 1 Reply Last reply
                                    0
                                    • K Kiran Satish

                                      Here is what happening in debug more

                                      snapshot(exposure)
                                      {
                                      set camera exposure
                                      start snapshotthread if not running
                                      SetEvent(getnewframe)
                                      ::WaitForSingleObject(waitfornewframe, INFINITE); -- set breakpoint(1)
                                      display the frame
                                      }
                                      .
                                      .
                                      .
                                      camthread
                                      {
                                      do
                                      {
                                      dummy variable -- set breakpoint(2)
                                      ::WaitForSingleObject(getnewframe, INFINITE);
                                      take a 20ms snapshot
                                      SetEvent(waitfornewframe);
                                      }while(camrunning)
                                      }

                                      when I hit snapshot button, the program stops at breakpoint(1) and then goes to breakpoint(2) and thats it from there it does nothing while the C++ interface shows as the program is in [run] mode. From the definitions of SetEvent and WaitForSingleObject I too dont see any thing wrong here. Do they need to be in actual two different threads, I dont think thats the case, interesting!

                                      PKNT

                                      M Offline
                                      M Offline
                                      Mark Salsbery
                                      wrote on last edited by
                                      #26

                                      hmm the only reason I see that could happen is if "take a 20ms snapshot" never returns so the waitfornewframe event gets set.

                                      Mark Salsbery Microsoft MVP - Visual C++ :java:

                                      K 3 Replies Last reply
                                      0
                                      • M Mark Salsbery

                                        hmm the only reason I see that could happen is if "take a 20ms snapshot" never returns so the waitfornewframe event gets set.

                                        Mark Salsbery Microsoft MVP - Visual C++ :java:

                                        K Offline
                                        K Offline
                                        Kiran Satish
                                        wrote on last edited by
                                        #27

                                        I even tired that too by commenting it out, but it does the same. will keep tryin different ways and see. Do you think I may need to define those event handles in CMutex objects :~ .

                                        PKNT

                                        M 1 Reply Last reply
                                        0
                                        • M Mark Salsbery

                                          hmm the only reason I see that could happen is if "take a 20ms snapshot" never returns so the waitfornewframe event gets set.

                                          Mark Salsbery Microsoft MVP - Visual C++ :java:

                                          K Offline
                                          K Offline
                                          Kiran Satish
                                          wrote on last edited by
                                          #28

                                          does it matter how I am defining my thread?? I am using DWORD WINAPI camclass::camthread(LPVOID) form.

                                          PKNT

                                          M 1 Reply Last reply
                                          0
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          • Login

                                          • Don't have an account? Register

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Categories
                                          • Recent
                                          • Tags
                                          • Popular
                                          • World
                                          • Users
                                          • Groups